Introduction

Project Objective

This project aims to build a workable model to predict if a client will subscribe a term deposit. Key features of clients who did subscribe a term deposit will be discovered and used for marketing. For example, what are the potential client group in terms of age, job, previous relationship with the bank, etc.

Data Processing

The project works on the original data set with 45211 observations and 17 attributes. The drawback of using the original data set is: the data set is unbalanced in the class label, with way more than no than yes. This gives the classification model a good accuracy but a bad specificity (the rate capturing the yes class.) Thus, in this project, the number of observation of “yes” in the “y” attribut will be replicated so that it equals to the number of observations of “no” in the “y” attribute.

About the attributes

Input variables: # bank client data: 1 - age (numeric) 2 - job : type of job (categorical: “admin.”,“unknown”,“unemployed”,“management”,“housemaid”,“entrepreneur”,“student”, “blue-collar”,“self-employed”,“retired”,“technician”,“services”) 3 - marital : marital status (categorical: “married”,“divorced”,“single”; note: “divorced” means divorced or widowed) 4 - education (categorical: “unknown”,“secondary”,“primary”,“tertiary”) 5 - default: has credit in default? (binary: “yes”,“no”) 6 - balance: average yearly balance, in euros (numeric) 7 - housing: has housing loan? (binary: “yes”,“no”) 8 - loan: has personal loan? (binary: “yes”,“no”) # related with the last contact of the current campaign: 9 - contact: contact communication type (categorical: “unknown”,“telephone”,“cellular”) 10 - day: last contact day of the month (numeric) 11 - month: last contact month of year (categorical: “jan”, “feb”, “mar”, …, “nov”, “dec”) 12 - duration: last contact duration, in seconds (numeric) # other attributes: 13 - campaign: number of contacts performed during this campaign and for this client (numeric, includes last contact) 14 - pdays: number of days that passed by after the client was last contacted from a previous campaign (numeric, -1 means client was not previously contacted) 15 - previous: number of contacts performed before this campaign and for this client (numeric) 16 - poutcome: outcome of the previous marketing campaign (categorical: “unknown”,“other”,“failure”,“success”)

Output variable (desired target): 17 - y - has the client subscribed a term deposit? (binary: “yes”,“no”)

For source and attribute info of the data set, please refer to https://archive.ics.uci.edu/ml/datasets/bank+marketing

Understand the data

Import the data set

library(tidyverse)
bank <- read_csv2("/home/junyan26/DATA/Bank_Marketing/bank/bank-full.csv", col_types = cols())
Using ',' as decimal and '.' as grouping mark. Use read_delim() for more control.
nrow(bank)
[1] 45211
ncol(bank)
[1] 17
print(head(bank))
sapply(bank, function(x) sum(is.na(x)))
      age       job   marital education   default   balance   housing      loan   contact       day     month 
        0         0         0         0         0         0         0         0         0         0         0 
 duration  campaign     pdays  previous  poutcome         y 
        0         0         0         0         0         0 

There is no missing data in the data set.

Multiply the number of “yes” observations in the “y” attribute._

library(splitstackshape)
bank <- as.data.frame((bank))
num_no <- nrow(bank[bank$y == "no", ])
num_yes <- nrow(bank[bank$y == "yes", ])
rep_num <- round(num_no/num_yes)
new_yes_rows <- expandRows(bank[bank$y == "yes", ], count = rep_num, count.is.col = FALSE)
print(nrow(new_yes_rows))
[1] 42312
print(num_no)
[1] 39922
n_yes_no <- data.frame(c(nrow(new_yes_rows), num_no))
n_yes_no$class <- c("yes", "no")
colnames(n_yes_no) <- c("count", "class")
n_yes_no <- n_yes_no[, c(2, 1)]
n_yes_no
library(ggplot2)
ggplot(data=n_yes_no, aes(x=reorder(class, -count), y=count, fill=reorder(class, -count))) +
  geom_bar(stat="identity", width=0.5)+ 
  theme_classic() + ylab("Count") + xlab("classes in target variable \"y\"") +
  ggtitle("Compare the Number of the Classes (After Over Sampling)") +
  theme(plot.title = element_text(hjust = 0.5, size=14, face="bold"), 
        axis.text.x = element_text(hjust = 0.5, face="bold", size=14))+
  guides(fill=FALSE, color=FALSE)

After the replication, the data is more balanced. The number of “yes” rows is 42312 and the number of “no” rows is 39922.

bank <- rbind(bank[bank$y == "no", ], new_yes_rows)
seed = 321
bank <- bank[sample(nrow(bank)),]
nrow(bank)
[1] 82234
ncol(bank)
[1] 17

The new data set has 82234 rows and 17 columns.

summary(bank)
      age            job              marital           education           default             balance      
 Min.   :18.00   Length:82234       Length:82234       Length:82234       Length:82234       Min.   : -8019  
 1st Qu.:32.00   Class :character   Class :character   Class :character   Class :character   1st Qu.:   127  
 Median :39.00   Mode  :character   Mode  :character   Mode  :character   Mode  :character   Median :   563  
 Mean   :41.27                                                                               Mean   :  1561  
 3rd Qu.:49.00                                                                               3rd Qu.:  1757  
 Max.   :95.00                                                                               Max.   :102127  
   housing              loan             contact               day           month              duration     
 Length:82234       Length:82234       Length:82234       Min.   : 1.00   Length:82234       Min.   :   0.0  
 Class :character   Class :character   Class :character   1st Qu.: 8.00   Class :character   1st Qu.: 145.0  
 Mode  :character   Mode  :character   Mode  :character   Median :15.00   Mode  :character   Median : 263.0  
                                                          Mean   :15.51                      Mean   : 383.8  
                                                          3rd Qu.:21.00                      3rd Qu.: 515.0  
                                                          Max.   :31.00                      Max.   :4918.0  
    campaign          pdays           previous         poutcome              y            
 Min.   : 1.000   Min.   : -1.00   Min.   :  0.000   Length:82234       Length:82234      
 1st Qu.: 1.000   1st Qu.: -1.00   1st Qu.:  0.000   Class :character   Class :character  
 Median : 2.000   Median : -1.00   Median :  0.000   Mode  :character   Mode  :character  
 Mean   : 2.483   Mean   : 53.03   Mean   :  0.846                                        
 3rd Qu.: 3.000   3rd Qu.: 75.00   3rd Qu.:  1.000                                        
 Max.   :63.000   Max.   :871.00   Max.   :275.000                                        

Turn the “character” class variables into “factor”.

sapply(bank, class)
        age         job     marital   education     default     balance     housing        loan     contact 
  "numeric" "character" "character" "character" "character"   "numeric" "character" "character" "character" 
        day       month    duration    campaign       pdays    previous    poutcome           y 
  "numeric" "character"   "numeric"   "numeric"   "numeric"   "numeric" "character" "character" 
c <- colnames(dplyr::select_if(bank, is.character))
bank[c] <- lapply(bank[c], factor)
sapply(bank, class)
      age       job   marital education   default   balance   housing      loan   contact       day     month 
"numeric"  "factor"  "factor"  "factor"  "factor" "numeric"  "factor"  "factor"  "factor" "numeric"  "factor" 
 duration  campaign     pdays  previous  poutcome         y 
"numeric" "numeric" "numeric" "numeric"  "factor"  "factor" 

Improved Model

Feature Engineering

  1. Generate a correlation heatmap and pair-wise scatter plot to visualize the big picture of the data.
  2. Visualize each variable and see its associatioin with the output variable “y”.
  3. Visualize those predictor variables that seem to be associated.
  4. Summerize the result

Turn categorical variables into numerical.

bank_numeric <- bank
sapply(bank_numeric, class)
      age       job   marital education   default   balance   housing      loan   contact       day     month 
"numeric"  "factor"  "factor"  "factor"  "factor" "numeric"  "factor"  "factor"  "factor" "numeric"  "factor" 
 duration  campaign     pdays  previous  poutcome         y 
"numeric" "numeric" "numeric" "numeric"  "factor"  "factor" 
must_convert <- sapply(bank_numeric,is.factor)
bank_numeric_temp <- sapply(bank_numeric[,must_convert],unclass)
bank_numeric <- cbind(bank_numeric[,!must_convert],bank_numeric_temp)
#Reorder the column names. Make them in sync with bank.
cnames = colnames(bank)
bank_numeric <- bank_numeric[, cnames]
head(bank_numeric)

“bank_numeric” is a dataset generated from “bank” and with all numerical data.

Examine the correlation and significant level(pvalue) between variables.

library(Hmisc)
Loading required package: survival

Attaching package: ‘survival’

The following object is masked from ‘package:caret’:

    cluster

Loading required package: Formula

Attaching package: ‘Hmisc’

The following objects are masked from ‘package:dplyr’:

    src, summarize

The following objects are masked from ‘package:base’:

    format.pval, units
options(scipen=999)
cor_p <- rcorr(x = data.matrix(bank_numeric)[, 1:16], y = bank_numeric[, 17],  type = c("pearson", "spearman"))

Correlation Matrx:

round(cor_p$r, 2)
            age   job marital education default balance housing  loan contact   day month duration campaign
age        1.00 -0.03   -0.45     -0.13   -0.02    0.11   -0.18 -0.03    0.03  0.00 -0.03    -0.01    -0.01
job       -0.03  1.00    0.08      0.15   -0.01    0.02   -0.14 -0.05   -0.09  0.02 -0.08     0.00    -0.01
marital   -0.45  0.08    1.00      0.13   -0.01    0.00   -0.03 -0.05   -0.06  0.00 -0.01     0.00    -0.02
education -0.13  0.15    0.13      1.00   -0.01    0.07   -0.11 -0.06   -0.12  0.01 -0.05    -0.02    -0.01
default   -0.02 -0.01   -0.01     -0.01    1.00   -0.06    0.02  0.08    0.03  0.01  0.00     0.00     0.02
balance    0.11  0.02    0.00      0.07   -0.06    1.00   -0.08 -0.09   -0.03  0.01  0.01     0.01    -0.02
housing   -0.18 -0.14   -0.03     -0.11    0.02   -0.08    1.00  0.09    0.22 -0.01  0.21     0.04     0.02
loan      -0.03 -0.05   -0.05     -0.06    0.08   -0.09    0.09  1.00    0.03  0.01  0.03     0.01     0.03
contact    0.03 -0.09   -0.06     -0.12    0.03   -0.03    0.22  0.03    1.00  0.00  0.28    -0.01     0.06
day        0.00  0.02    0.00      0.01    0.01    0.01   -0.01  0.01    0.00  1.00 -0.02    -0.01     0.13
month     -0.03 -0.08   -0.01     -0.05    0.00    0.01    0.21  0.03    0.28 -0.02  1.00     0.00    -0.09
duration  -0.01  0.00    0.00     -0.02    0.00    0.01    0.04  0.01   -0.01 -0.01  0.00     1.00    -0.03
campaign  -0.01 -0.01   -0.02     -0.01    0.02   -0.02    0.02  0.03    0.06  0.13 -0.09    -0.03     1.00
pdays      0.00  0.00    0.02      0.02   -0.03    0.02    0.07 -0.04   -0.23 -0.06  0.03    -0.04    -0.10
previous   0.02  0.02    0.02      0.03   -0.03    0.03    0.00 -0.03   -0.16 -0.05  0.03    -0.03    -0.05
poutcome  -0.01 -0.01   -0.02     -0.04    0.04   -0.03   -0.05  0.04    0.25  0.06 -0.04     0.05     0.11
y          0.03  0.06    0.07      0.10   -0.04    0.08   -0.22 -0.12   -0.26 -0.04 -0.04     0.45    -0.13
          pdays previous poutcome     y
age        0.00     0.02    -0.01  0.03
job        0.00     0.02    -0.01  0.06
marital    0.02     0.02    -0.02  0.07
education  0.02     0.03    -0.04  0.10
default   -0.03    -0.03     0.04 -0.04
balance    0.02     0.03    -0.03  0.08
housing    0.07     0.00    -0.05 -0.22
loan      -0.04    -0.03     0.04 -0.12
contact   -0.23    -0.16     0.25 -0.26
day       -0.06    -0.05     0.06 -0.04
month      0.03     0.03    -0.04 -0.04
duration  -0.04    -0.03     0.05  0.45
campaign  -0.10    -0.05     0.11 -0.13
pdays      1.00     0.46    -0.80  0.15
previous   0.46     1.00    -0.51  0.14
poutcome  -0.80    -0.51     1.00 -0.12
y          0.15     0.14    -0.12  1.00
round(cor_p$P, 2)
           age  job marital education default balance housing loan contact  day month duration campaign pdays
age         NA 0.00    0.00      0.00    0.00    0.00    0.00    0    0.00 0.59  0.00     0.12     0.02  0.21
job       0.00   NA    0.00      0.00    0.00    0.00    0.00    0    0.00 0.00  0.00     0.87     0.02  0.88
marital   0.00 0.00      NA      0.00    0.01    0.44    0.00    0    0.00 0.46  0.01     0.71     0.00  0.00
education 0.00 0.00    0.00        NA    0.00    0.00    0.00    0    0.00 0.02  0.00     0.00     0.02  0.00
default   0.00 0.00    0.01      0.00      NA    0.00    0.00    0    0.00 0.05  0.34     0.25     0.00  0.00
balance   0.00 0.00    0.44      0.00    0.00      NA    0.00    0    0.00 0.07  0.02     0.00     0.00  0.00
housing   0.00 0.00    0.00      0.00    0.00    0.00      NA    0    0.00 0.02  0.00     0.00     0.00  0.00
loan      0.00 0.00    0.00      0.00    0.00    0.00    0.00   NA    0.00 0.00  0.00     0.00     0.00  0.00
contact   0.00 0.00    0.00      0.00    0.00    0.00    0.00    0      NA 0.64  0.00     0.11     0.00  0.00
day       0.59 0.00    0.46      0.02    0.05    0.07    0.02    0    0.64   NA  0.00     0.04     0.00  0.00
month     0.00 0.00    0.01      0.00    0.34    0.02    0.00    0    0.00 0.00    NA     0.68     0.00  0.00
duration  0.12 0.87    0.71      0.00    0.25    0.00    0.00    0    0.11 0.04  0.68       NA     0.00  0.00
campaign  0.02 0.02    0.00      0.02    0.00    0.00    0.00    0    0.00 0.00  0.00     0.00       NA  0.00
pdays     0.21 0.88    0.00      0.00    0.00    0.00    0.00    0    0.00 0.00  0.00     0.00     0.00    NA
previous  0.00 0.00    0.00      0.00    0.00    0.00    0.86    0    0.00 0.00  0.00     0.00     0.00  0.00
poutcome  0.05 0.10    0.00      0.00    0.00    0.00    0.00    0    0.00 0.00  0.00     0.00     0.00  0.00
y         0.00 0.00    0.00      0.00    0.00    0.00    0.00    0    0.00 0.00  0.00     0.00     0.00  0.00
          previous poutcome  y
age           0.00     0.05  0
job           0.00     0.10  0
marital       0.00     0.00  0
education     0.00     0.00  0
default       0.00     0.00  0
balance       0.00     0.00  0
housing       0.86     0.00  0
loan          0.00     0.00  0
contact       0.00     0.00  0
day           0.00     0.00  0
month         0.00     0.00  0
duration      0.00     0.00  0
campaign      0.00     0.00  0
pdays         0.00     0.00  0
previous        NA     0.00  0
poutcome      0.00       NA  0
y             0.00     0.00 NA
#install.packages("corrplot")
library(corrplot)
corrplot 0.84 loaded
M <- cor_p$r
p_mat <- cor_p$P
corrplot(M, type = "upper",col = heat.colors(100),bg = "darkblue",
         p.mat = p_mat, sig.level = 0.05)

__The correlation matrix is using Pearson Correlation and Spearman’s Rank Correlation, the formaer of which designed for ratio and intervel data, while the latter of which for ordinal, ratio and interval. It is not very accuray because the not all the variables are of these data type. Still , it can be a reference of how the data seem to be correlated.

In the majority of analyses, an alpha of 0.05 is used as the cutoff for significance. If the p-value is less than 0.05, we reject the null hypothesis that there’s no correlation between the variables and conclude that a significant correlation does exist. If the p-value is larger than 0.05, we CANNOT reject the null hypothese and CANNOT conclude that a significant correlation exists.

The crosses in the plot indicate p-value higher than 0.05, which means we cannot conclude that the correlation between the varibales is significant. For the rest of them, we can conclude that the corralation is significant.

Thus, all variables has significant correlation with the “y”, although the correlation coefficients are not high.

The correlation between “pdays” and “poutcome” is negatively strong, which need to look closer later.

Scatterplot Matrix

The points in a scatterplot matrix can be colored by the class label in classification problems. This can help to spot clear (or unclear) separation of classes and perhaps give an idea of how difficult the problem may.

pairs(y~., data = as.matrix(bank), col = bank$y, lower.panel = NULL)

The pair plot shows how the variable pairs can seperate the “y” variable. Overall, the seperation is not very clear. Categorical and numerical variables are also included, which makes the plot unnecessarily big.

Levels of the factor variables:

sapply(dplyr::select_if(bank, is.factor), levels)
$job
 [1] "admin."        "blue-collar"   "entrepreneur"  "housemaid"     "management"    "retired"      
 [7] "self-employed" "services"      "student"       "technician"    "unemployed"    "unknown"      

$marital
[1] "divorced" "married"  "single"  

$education
[1] "primary"   "secondary" "tertiary"  "unknown"  

$default
[1] "no"  "yes"

$housing
[1] "no"  "yes"

$loan
[1] "no"  "yes"

$contact
[1] "cellular"  "telephone" "unknown"  

$month
 [1] "apr" "aug" "dec" "feb" "jan" "jul" "jun" "mar" "may" "nov" "oct" "sep"

$poutcome
[1] "failure" "other"   "success" "unknown"

$y
[1] "no"  "yes"

Summary of numeric variable:

sapply(dplyr::select_if(bank, is.numeric), summary)
             age    balance     day  duration campaign     pdays   previous
Min.    18.00000  -8019.000  1.0000    0.0000  1.00000  -1.00000   0.000000
1st Qu. 32.00000    127.000  8.0000  145.0000  1.00000  -1.00000   0.000000
Median  39.00000    563.000 15.0000  263.0000  2.00000  -1.00000   0.000000
Mean    41.26661   1561.265 15.5146  383.8323  2.48345  53.03128   0.845964
3rd Qu. 49.00000   1757.000 21.0000  515.0000  3.00000  75.00000   1.000000
Max.    95.00000 102127.000 31.0000 4918.0000 63.00000 871.00000 275.000000

From summary, we can see “age” seems normally distributed. “balance” is very widely spread. “day” itself doesn’t make a lot of sense and may be combined with “month”. “duration”, “campaign”, “pdays” and “previous” seem to be skewed.

Visualization of the Data Set

ggplot(bank, aes(x=age, fill=y, color=y))+
  geom_histogram(position="identity", alpha=0.5, binwidth=2)+
  stat_function(fun = function(bank, mean, sd, n){
    n * dnorm(x = bank, mean = mean, sd = sd) }, 
    args = with(bank, c(mean = mean(age), sd = sd(age), n 
                        = length(age))), col="blue") +
  scale_color_manual(values=c("lightblue", "coral"))+
  scale_fill_manual(values=c("lightblue", "coral")) +
  theme_classic() +
  ggtitle("Age Distribution Colored by Y") +
  theme(plot.title = element_text(hjust = 0.5, size=14, face="bold"))

Red and blue area seperated a little, but overal, it is overlap. Age is nomally distributed.

spineplot(x = bank$age, y = bank$y, xlab = "age", ylab = "y", breaks=20,
          main = "Age vs Y", col = c("lightblue",  "coral"))

More than 80% of people aged older than 60 and younger than 18 subsribe a term deposit. But this two groups are only small number of people.

ggplot(bank, aes(x=balance, fill=y, color=y))+
  geom_histogram(position="identity", alpha=0.5, binwidth=2000)+
  scale_color_manual(values=c("lightblue", "coral"))+
  scale_fill_manual(values=c("lightblue", "coral")) +
  theme_classic() +
  ggtitle("Balance Distribution Colored by Y") +
  theme(plot.title = element_text(hjust = 0.5, size=14, face="bold"))

“balance” is not normally distributed. The two color has a little seperation.

spineplot(x = bank$balance, y = bank$y, xlab = "balance", ylab = "y", breaks=100,
          main = "balance vs y", col = c("lightblue",  "coral"))

spineplot(x = bank[bank$balance > 25000, "balance"], y = bank[bank$balance > 25000, "y"], xlab = "balance", ylab = "y", breaks=100,
          main = "balance (>25000) vs y", col = c("lightblue",  "coral"))

Balance larger than 25000 has more “yes”.

bank[bank$balance > 65000, c("balance", "y")]
spineplot(x = bank[bank$balance < -600, "balance"], y = bank[bank$balance < -600, "y"], xlab = "balance", ylab = "y", breaks=100,
          main = "balance (<0) vs y", col = c("lightblue",  "coral"))

It seems like for balance more than 25000, the more balance, the higher proportion of people subsribe a term deposit. But this is very small number of group. For negative balance, there are less people subscribe.

Plot age, balance and y

#library(ggplot2)
library(ggpubr)
Loading required package: magrittr

Attaching package: ‘magrittr’

The following object is masked from ‘package:purrr’:

    set_names

The following object is masked from ‘package:tidyr’:

    extract
# Grouped Scatter plot with marginal density plots
ggscatterhist(
  bank[, c("age", "balance", "y")], x = "age", y = "balance",
  color = "y", size = 3, alpha = 0.6,
  palette = c("lightblue", "coral"), legend = "right")

The two colors are almost well moix. “age” and “balance” together don’t seperate “yes” and “no” in “y” well.

ggplot(bank, aes(x=duration, fill=y, color=y))+
  geom_histogram(position="identity", alpha=0.5, binwidth=100)+
  scale_color_manual(values=c("lightblue", "coral"))+
  scale_fill_manual(values=c("lightblue", "coral")) +
  theme_classic() +
  ggtitle("Duration Distribution Colored by Y") +
  theme(plot.title = element_text(hjust = 0.5, size=14, face="bold"))+
  xlab("duration: last contact duration, in seconds")

Two colors have a obvious seperation. “duration” seperates the two categories of “y” pretty well.

spineplot(x = bank$duration, y = bank$y, xlab = "duration: last contact duration, in seconds", ylab = "y", breaks=100,
          main = "Duration vs Y", col = c("lightblue",  "coral"))

“duration” and “y”are pretty strongly associated. The longer duration is, the bigger prportion of people subscibe a term deposit.

summary(bank$duration)
   Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
    0.0   145.0   263.0   383.8   515.0  4918.0 
ggplot(bank, aes(x=campaign, fill=y, color=y))+
  geom_histogram(position="identity", alpha=0.5, binwidth=1)+
  scale_color_manual(values=c("lightblue", "coral"))+
  scale_fill_manual(values=c("lightblue", "coral")) +
  theme_classic() +
  ggtitle("Campaign Distribution Colored by Y") +
  theme(plot.title = element_text(hjust = 0.5, size=14, face="bold")) +
  xlab("campaign: number of contacts performed during this campaign")

The two colors overlap well in the middle and have some seperation at the two ends.

summary(bank$campaign)
   Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
  1.000   1.000   2.000   2.483   3.000  63.000 

Unique number in “campaign”:

sort(unique(bank$campaign))
 [1]  1  2  3  4  5  6  7  8  9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35
[36] 36 37 38 39 41 43 44 46 50 51 55 58 63
aggregate(data.frame(count = bank$campaign), list(value = bank$campaign), length)

Most of the campaign is on 1 and 2.

spineplot(x = bank$campaign, y = bank$y, xlab = "campaign: number of contacts performed during this campaign", ylab = "y", breaks=50,
          main = "Campaign vs Y", col = c("lightblue",  "coral"))

Seem to have some trend.

df_cam <- bank[bank$campaign > 3, ]
spineplot(x = df_cam$campaign, y = df_cam$y, xlab = "campaign: number of contacts performed during this campaign", ylab = "y", breaks=50, main = "Campaign (>3) vs Y", col = c("lightblue",  "coral"))

There is a trend that the more number of campaign, the less percentage of clients substribe a term deposit, Expecially for campaign more than 3.

library(ggpubr)
# Grouped Scatter plot with marginal density plots
ggscatterhist(
  bank[, c("campaign", "duration", "y")], x = "campaign", y = "duration",
  color = "y", size = 3, alpha = 0.6,
  palette = c("lightblue", "coral"), legend = "right")+
  ggtitle("Campaign and Duration Colored by Y") +
  theme(plot.title = element_text(hjust = 0.5, size=14, face="bold"))

The two colors seperated. May be because “duration” is a predictor. But “campaign” should be kept as well.

summary(bank$previous)
   Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
  0.000   0.000   0.000   0.846   1.000 275.000 

Unique number in “previous”:

sort(unique(bank$previous))
 [1]   0   1   2   3   4   5   6   7   8   9  10  11  12  13  14  15  16  17  18  19  20  21  22  23  24  25
[27]  26  27  28  29  30  32  35  37  38  40  41  51  55  58 275
aggregate(data.frame(count = bank$previous), list(value = bank$previous), length)
library(ggplot2)
ggplot(bank, aes(x=previous, fill=y, color=y))+
  geom_histogram(position="identity", alpha=0.5, binwidth=10)+
  scale_color_manual(values=c("lightblue", "coral"))+
  scale_fill_manual(values=c("lightblue", "coral")) +
  theme_classic() +
  ggtitle("Previous Distribution Colored by Y") +
  theme(plot.title = element_text(hjust = 0.5, size=14, face="bold")) +
  xlab("previous: number of contacts performed before this campaign")

Previous is not unbalanced. Remove previous > 10 and plot again

library(ggplot2)
df_prev <- bank[bank$previous < 10,]
ggplot(df_prev, aes(x=previous, fill=y, color=y))+
  geom_histogram(position="identity", alpha=0.5, binwidth=1)+
  scale_color_manual(values=c("lightblue", "coral"))+
  scale_fill_manual(values=c("lightblue", "coral")) +
  theme_classic() +
  ggtitle("Previous ( < 10) Distribution Colored by Y") +
  theme(plot.title = element_text(hjust = 0.5, size=14, face="bold"))+
  xlab("previous: number of contacts performed before this campaign")

Two colors has some sort of seperation.

spineplot(x = bank$previous, y = bank$y, xlab = "previous: number of contacts performed before this campaign", ylab = "y", breaks=200, main = "Previous vs Y", col = c("lightblue",  "coral"))

Less than 50% of people that has no previous contact subscribe a term deposit. And most people don’t have previous contact. And more previous contact seems to lead to more percentage of people who subscribe a term deposit. May be previous should be convert into boolean, 0 = false, >0 = true

tem <- bank
tem$previous1 <- tem$previous > 0
tem$previous1 <- as.factor(tem$previous1)
spineplot(x = tem$previous1, y = tem$y, xlab = "Previous (bool)", ylab = "y", breaks=lims,
          main = "Previous (bool) vs Y", col = c("lightblue", "coral"), xaxlabels = levels(tem$previous1))

Now the variable makes more sense.

library(ggplot2)
ggplot(bank, aes(x=pdays, fill=y, color=y))+
  geom_histogram(position="identity", alpha=0.5, binwidth=100)+
  scale_color_manual(values=c("lightblue", "coral"))+
  scale_fill_manual(values=c("lightblue", "coral")) +
  theme_classic() +
  ggtitle("Pdays Distribution Colored by Y") +
  theme(plot.title = element_text(hjust = 0.5, size=14, face="bold"))+
  xlab("pdays: number of days that passed by after the client was \nlast contacted from a previous campaign (-1 means client was not previously contacted)")

“Pdays” are most -1 (-1 means client was not previously contacted). So this variable should have a lot of duplicate as the “previous” variable.

library(ggplot2)
df_pdays <- bank[bank$pdays == -1,]
ggplot(df_pdays, aes(x=pdays, fill=y, color=y))+
  geom_histogram(position="identity", alpha=0.3, binwidth=0.5)+
  scale_color_manual(values=c("lightblue", "coral"))+
  scale_fill_manual(values=c("lightblue", "coral")) +
  theme_classic() +
  ggtitle("Pdays (=-1) Distribution Colored by Y") +
  theme(plot.title = element_text(hjust = 0.5, size=14, face="bold"))+
  xlab("pdays: number of days that passed by after the client was \nlast contacted from a previous campaign (-1 means client was not previously contacted)")

spineplot(x = bank$pdays, y = bank$y, xlab = "pdays", ylab = "y", breaks=1000, main = "Pdays vs Y", col = c("lightblue",  "coral"))

df_pdays <- bank[bank$pdays >0, ]
spineplot(x = df_pdays$pdays, y = df_pdays$y, xlab = "pdays", ylab = "y", breaks=100, main = "Pdays (>0) vs Y", col = c("lightblue",  "coral"))

bank[bank$pdays>0,]

Most clients didn’t get contact previous, and less than half of them subscribe a term deposit. For those who got contacted, pdays 80-100 and 180-190 have the highest subscribe rate (more than 80%).

Convert “pdays” into bool

tem$pdays1 <- tem$pdays > 0
tem$pdays1 <- as.factor(tem$pdays1)
spineplot(x = tem$pdays1, y = tem$y, xlab = "Pdays (bool)", ylab = "y", breaks=lims,
          main = "Pdays (bool) vs Y", col = c("lightblue", "coral"), xaxlabels = levels(tem$pdays1))

Compare previous and pdays

tem$previous1 <- as.factor(tem$previous1)
spineplot(x = tem$pdays1, y = tem$previous1, xlab = "Pdays (bool)", ylab = "y", breaks=lims,
          main = "Pdays (bool) vs Previous (bool) ", col = c("lightblue", "coral"), xaxlabels = levels(tem$pdays1))

pdays (bool) and previous (bool) are total overlap. Since pdays is strongly associated with previous and poutcome (shown later). It should be droped when performing classification.

Combine cpmpaign and previous, named “contact_times”

tem$contact_times <- tem$campaign + tem$previous
library(ggplot2)
ggplot(tem, aes(x=contact_times, fill=y, color=y))+
  geom_histogram(position="identity", alpha=0.5, binwidth=5)+
  scale_color_manual(values=c("lightblue", "coral"))+
  scale_fill_manual(values=c("lightblue", "coral")) +
  theme_classic() +
  ggtitle("Contact_Times (Campaign + Previous) Distribution Colored by Y") +
  theme(plot.title = element_text(hjust = 0.5, size=14, face="bold"))+
  xlab("contact_times: the total number of being contacted \nbefore this campaign and during this compaign")

spineplot(x = tem$contact_times, y = tem$y, xlab = "contact_times: the total number of being contacted \nbefore this campaign and during this compaign", ylab = "y", breaks=200,
          main = "Contact_Times (Campaign + Previous) vs Y", col = c("lightblue",  "coral"))

The contact_times variable shows less power of splitting “yes” and “no” in the y variable. So it should not be used.

Plot month:

ggplot(bank, aes(x=month, fill=y, color=y))+
  geom_histogram(position="identity", alpha=0.5, stat="count")+
  scale_color_manual(values=c("lightblue", "coral"))+
  scale_fill_manual(values=c("lightblue", "coral")) +
  theme_classic() +
  ggtitle("Month Distribution Colored by Y") +
  theme(plot.title = element_text(hjust = 0.5, size=14, face="bold"))
Ignoring unknown parameters: binwidth, bins, pad

Two colors have some sort of seperation.

Plot day of month:

ggplot(bank, aes(x=day, fill=y, color=y))+
  geom_histogram(position="identity", alpha=0.5, stat="count")+
  scale_color_manual(values=c("lightblue", "coral"))+
  scale_fill_manual(values=c("lightblue", "coral")) +
  theme_classic() +
  ggtitle("Day Distribution Colored by Y") +
  theme(plot.title = element_text(hjust = 0.5, size=14, face="bold"))
Ignoring unknown parameters: binwidth, bins, pad

Two colors have some seperation.

Combine day and month

summary(bank[, c("day", "month")])
      day            month      
 Min.   : 1.00   may    :20241  
 1st Qu.: 8.00   jul    :11284  
 Median :15.00   aug    :11063  
 Mean   :15.51   jun    : 9163  
 3rd Qu.:21.00   apr    : 6971  
 Max.   :31.00   nov    : 6791  
                 (Other):16721  
library(magrittr)
library(lubridate)

Attaching package: ‘lubridate’

The following object is masked from ‘package:base’:

    date
mo2Num <- function(x) match(tolower(x), tolower(month.abb))
month <- as.double(mo2Num(bank$month))
day <- as.double(bank$day)
m_d <- paste("2018", month, day, sep="-") %>% ymd() %>% as.Date()
m_d <- setNames(data.frame(m_d), "date")
head(m_d)
tem <- cbind(tem, m_d)
#remove the year
tem$date <- format(tem$date, format="%m-%d")
tem <- tem[, -19]
head(tem)

Plot date (day and month)

length(unique(tem$date))
[1] 318

Group “y” by each unique date

library(dplyr)
date_y <- tem %>% 
  group_by(date, y) %>% 
  summarise(n = n())
head(date_y)
date_y <- data.frame(date_y)
date_y_yes <- date_y[date_y$y=="yes", c("date", "n")]
rownames(date_y_yes) <- date_y_yes$date
head(date_y_yes)
date_y_no <- date_y[date_y$y=="no", c("date", "n")]
rownames(date_y_no) <- date_y_no$date
head(date_y_no)
ggplot(data=date_y, aes(x=date, y= n, group = y, fill=y, color=y)) + geom_line()+
  scale_color_manual(values=c("lightblue", "coral"))+
  scale_fill_manual(values=c("lightblue", "coral")) +
  theme_classic() +
  ggtitle("Date Distribution Colored by Y")+
  theme(plot.title = element_text(hjust = 0.5, size=14, face="bold"), axis.text.x= element_blank()) +
  ylab("count")

The two colors seperated a little, but overall it seems to be the same as month. So it shoube be better to keep month and day seperated.

ggplot(bank, aes(x=poutcome, fill=y, color=y))+
  geom_histogram(position="identity", alpha=0.5, stat="count")+
  scale_color_manual(values=c("lightblue", "coral"))+
  scale_fill_manual(values=c("lightblue", "coral")) +
  theme_classic() +
  ggtitle("Poutcome Distribution Colored by Y") +
  theme(plot.title = element_text(hjust = 0.5, size=14, face="bold"))
Ignoring unknown parameters: binwidth, bins, pad

Success has much more “yes” than “no”.

levels(bank$poutcome)
[1] "failure" "other"   "success" "unknown"

From the pair plot, “pdays” and “poutcome” together can identify most one category of “y” and can barely identify the other. “y” is boolean, which red correla

par(las = 2, cex.axis = 0.75, mar = c(7, 4.1, 4.1, 2.1))
spineplot(x = bank$poutcome, y = bank$y, xlab = "poutcome", ylab = "y", breaks=lims,
          main = "poutcome vs y", col = c("lightblue",  "coral"))

If a client subsribed a term deposit, he/ she is very likely to subscribe in this campaign. Poutcome seems to be a strong predictor.

Plot pdays and poutcome

par(las = 1, cex.axis = 0.75, mar = c(7, 4.1, 4.1, 2.1))
spineplot(x = bank$pday, y = bank$poutcome, xlab = "pdays", ylab = "poutcome", breaks=1000,
          main = "pdays vs poutcome", col = c("lightblue", "grey","coral", "white"))

dfpp <- bank[bank$pdays >-1, ]
par(las = 1, cex.axis = 0.75, mar = c(7, 4.1, 4.1, 2.1))
spineplot(x = dfpp$pday, y = dfpp$poutcome, xlab = "pdays", ylab = "poutcome", breaks=2000,
          main = "pdays vs poutcome", col = c("lightblue", "grey","coral", "white"))

All poutcome is unknown if pdays = -1, meaning the client was not contacted from a previous compaign.

Most of the clients with pdays of 88-93 and 176 - 186 that were previously contacted subscribed a term deposit.

Pdays seems to be strongly correlated to poutcome. So it may be removed.

par(las = 2, cex.axis = 0.75, mar = c(7, 4.1, 4.1, 2.1))
spineplot(x = bank$job, y = bank$y, xlab = "job", ylab = "y",
          main = "Job vs Y", col = c("lightblue", "coral"), xaxlabels = levels(bank$job))

Overall, job has some difference in “yes” and “no” among its categories. It may be a good predictor.

spineplot(x = bank$marital, y = bank$y, xlab = "marital", ylab = "y",
          main = "Marital vs Y", col = c("lightblue", "coral"), xaxlabels = levels(bank$marital))

Both marital have equal number of “yes” and “no” among its categories. It seems to be a very weak predictor.

spineplot(x = bank$job, y = bank$marital, xlab = "marital", ylab = "job",
          main = "Marital vs Job", col = c("lightblue", "coral", "grey"), xaxlabels = levels(bank$job))

Most students are single and most retired peopele are married. But the two variables are pretty mixed overall. So they seem not strongly associated.

spineplot(x = bank$education, y = bank$y, xlab = "education", ylab = "y",
          main = "Education vs Y", col = c("lightblue", "coral"), xaxlabels = levels(bank$education))

The “yes” and “no” are pretty evenly seperated. “education” may not be a good predictor.

spineplot(x = bank$default, y = bank$y, xlab = "default", ylab = "y",
          main = "Default vs Y", col = c("lightblue", "coral"), xaxlabels = levels(bank$default))

“default” almost perfectly split yes and no. Obviously, it has almost not association with “y”.

par(las = 2, cex.axis = 0.75, mar = c(7, 4.1, 4.1, 2.1))
spineplot(x = bank$job, y = bank$default, xlab = "job", ylab = "default",
          main = "Default vs Job", col = c("lightblue", "coral"), xaxlabels = levels(bank$job))

Almost all retired and student clients have no defult. And all other jobs have default. Default has very small number “yes” and it doesn’t identify “y” well. “default” can be deleted.

spineplot(x = bank$housing, y = bank$y, xlab = "housing", ylab = "y",
          main = "Housing vs Y", col = c("lightblue", "coral"), xaxlabels = levels(bank$housing))

“housing” has some difference in the proportion of “yes” and “no”. But not very obvious.

par(las = 2, cex.axis = 0.75, mar = c(7, 4.1, 4.1, 2.1))
spineplot(x = bank$job, y = bank$housing, xlab = "job", ylab = "housing",
          main = "Job vs Housing", col = c("lightblue", "coral"), xaxlabels = levels(bank$job))

“housing” is widely spread to all jobs. so “housing” seems to have weak association with “job”.

ggplot(bank, aes(x=age, fill=housing, color=housing))+
  geom_histogram(position="identity", alpha=0.5)+
  stat_function(fun = function(bank, mean, sd, n){
    n * dnorm(x = bank, mean = mean, sd = sd) }, 
    args = with(bank, c(mean = mean(age), sd = sd(age), n 
                        = length(age))), col="blue") +
  scale_color_manual(values=c("lightblue", "coral"))+
  scale_fill_manual(values=c("lightblue", "coral")) +
  theme_classic() +
  ggtitle("Balance Distribution Colored by Housing") +
  theme(plot.title = element_text(hjust = 0.5, size=14, face="bold"))

“housing” seems to associated with “age”. Correlation of these two should be tested.

spineplot(x = bank$balance, y = bank$housing, xlab = "balance", ylab = "housing", breaks=100,
          main = "Balance vs Housing", col = c("lightblue",  "coral"))

“housing” seems to widely in balance.

spineplot(x = bank$loan, y = bank$y, xlab = "loan", ylab = "y",
          main = "Loan vs Y", col = c("lightblue", "coral"), xaxlabels = levels(bank$loan))

No dramactic split.

spineplot(x = bank$housing, y = bank$loan, xlab = "housing", ylab = "loan",
          main = "Housing vs Loan", col = c("lightblue", "coral"), xaxlabels = levels(bank$housing))

No strong association.

par(las = 2, cex.axis = 0.75, mar = c(7, 4.1, 4.1, 2.1))
spineplot(x = bank$contact, y = bank$y, xlab = "contact", ylab = "y",
          main = "Contact vs Y", col = c("lightblue", "coral"), xaxlabels = levels(bank$contact))

May have some association.

Summry of the plots

In the above visualization, some variable seem to have more association than the others. Here is the summery of the predictor variables:

Good: duration, job, age, poucome medium: month, day, previous (bool) small: marital, education, balance, housing, loan, contact, campaign should not use: default, pdays

Tests of Independence

Run the tests to further identify the strength of the predictor variables.

Chi-Squared Test

Chi-Squared Test is for two categorical variables. Variables include: job, poutcome, month, previous(bool), education, housing, loan, contact, default

Between predictor variables: job and default

  1. Generate frequecy table
  2. Chi-squared Test
  3. Cramer’s V (to get the association strength)
  4. Compare the result and select the features together with the visualization summary

Sample 1% of data for chi-squared test of independence.

set.seed(789)
inde_test <- bank[sample(seq_len(nrow(bank)), size = floor(0.01 * nrow(bank))), ]
nrow(inde_test)
[1] 822

Contingency tables of y ~ job, poutcome, month, day (need to convert to factor), previous (bool), education, housing, loan, contact and default

job

Chi-squared test: H0: The two variables are independent. H1: The two variables are related.

y_job <- table(inde_test$job, inde_test$y)
y_job
               
                 no yes
  admin.         39  57
  blue-collar    90  62
  entrepreneur   12  13
  housemaid       9   9
  management     79 101
  retired        20  39
  self-employed  15  13
  services       41  31
  student         5  29
  technician     49  76
  unemployed     13  16
  unknown         1   3

Pooling: When a variable has more than two categories, and some of them have small numbers, it often makes sense to pool some of the categories together.

Combine “unknown”, “housemaid” and student into a new category “others”

jobs <- levels(inde_test$job)
jobs
 [1] "admin."        "blue-collar"   "entrepreneur"  "housemaid"     "management"    "retired"      
 [7] "self-employed" "services"      "student"       "technician"    "unemployed"    "unknown"      
library(rockchalk)

Attaching package: ‘rockchalk’

The following object is masked from ‘package:Hmisc’:

    summarize

The following object is masked from ‘package:dplyr’:

    summarize
inde_test$new_jobs <- combineLevels(inde_test$job, levs = c("student", "housemaid", "unknown", 
                                                            "unemployed", "entrepreneur"), 
                                    newLabel = c("Others"))
The original levels admin. blue-collar entrepreneur housemaid management retired self-employed services student technician unemployed unknown 
have been replaced by admin. blue-collar management retired self-employed services technician Others 
y_new_job <- table(inde_test$new_job, inde_test$y)
y_new_job
               
                 no yes
  admin.         39  57
  blue-collar    90  62
  management     79 101
  retired        20  39
  self-employed  15  13
  services       41  31
  technician     49  76
  Others         40  70
chisq.test(y_new_job)

    Pearson's Chi-squared test

data:  y_new_job
X-squared = 26.082, df = 7, p-value = 0.0004869

Critical Value (use 95% confidence level). (If Chi Square value >= Critical Value, reject the null hypothesis. If Chi Square value < Critical Value, fail to reject the null hypothesis.)

df = (8-1) * (2-1)
qchisq(0.95, df)
[1] 14.06714
library(questionr)

Attaching package: ‘questionr’

The following objects are masked from ‘package:Hmisc’:

    describe, wtd.mean, wtd.table, wtd.var
job_v <- cramer.v(y_new_job)
job_v
[1] 0.1781296

p-value is very small and chi-squared value is larger than critical value. So reject null hypothesis. There is association between job and y. The association is medium strong based on the cramer’s v value.

poutcome

library(questionr)
print("Contingency Table of Poutcome and Y:")
[1] "Contingency Table of Poutcome and Y:"
po_y <- table(inde_test$poutcome, inde_test$y)
po_y 
         
           no yes
  failure  37  66
  other    11  26
  success   5  76
  unknown 320 281
print("Chi-Squared Test of Poutcome and Y Table:")
[1] "Chi-Squared Test of Poutcome and Y Table:"
chisq.test(po_y) 

    Pearson's Chi-squared test

data:  po_y
X-squared = 72.605, df = 3, p-value = 0.000000000000001181
df <- (length(levels(inde_test$poutcome)) -1 ) * (length(levels(inde_test$y)) -1 )
print("Critical Value of Poutcome and Y Table:")
[1] "Critical Value of Poutcome and Y Table:"
qchisq(0.95, df)
[1] 7.814728
print("Cramer.V of Poutcome and Y:")
[1] "Cramer.V of Poutcome and Y:"
poutcome_v <- cramer.v(po_y)
poutcome_v
[1] 0.2971998

“poutcome” is associated with “y”. The association is pretty strong as cramer’s v larger than 0.3.

month

library(questionr)
print("Contingency Table of Month and Y:")
[1] "Contingency Table of Month and Y:"
month_y <- table(inde_test$month, inde_test$y)
month_y 
     
       no yes
  apr  24  54
  aug  60  55
  dec   0  10
  feb  27  45
  jan  17  11
  jul  43  53
  jun  39  41
  mar   6  17
  may 113  86
  nov  37  33
  oct   3  22
  sep   4  22
print("Chi-Squared Test of Month and Y Table:")
[1] "Chi-Squared Test of Month and Y Table:"
chisq.test(month_y) 
Chi-squared approximation may be incorrect

    Pearson's Chi-squared test

data:  month_y
X-squared = 58.158, df = 11, p-value = 0.00000002035
df <- (length(levels(inde_test$month)) -1 ) * (length(levels(inde_test$y)) -1 )
print("Critical Value of Month and Y Table:")
[1] "Critical Value of Month and Y Table:"
qchisq(0.95, df)
[1] 19.67514
print("Cramer.V of Month and Y:")
[1] "Cramer.V of Month and Y:"
month_v <- cramer.v(month_y)
Chi-squared approximation may be incorrect
month_v
[1] 0.265992

“month” is associated with “y”, and the association is pretty strong.

previous (bool)

change previuos into bool

inde_test$previous_bool <- inde_test$previous > 0
inde_test$previous_bool <- as.factor(inde_test$previous_bool)
library(questionr)
print("Contingency Table of Previous_bool and Y:")
[1] "Contingency Table of Previous_bool and Y:"
previous_bool_y <- table(inde_test$previous_bool, inde_test$y)
previous_bool_y 
       
         no yes
  FALSE 320 280
  TRUE   53 169
print("Chi-Squared Test of Previous_bool and Y Table:")
[1] "Chi-Squared Test of Previous_bool and Y Table:"
chisq.test(previous_bool_y) 

    Pearson's Chi-squared test with Yates' continuity correction

data:  previous_bool_y
X-squared = 55.555, df = 1, p-value = 0.00000000000009087
df <- (length(levels(inde_test$previous_bool)) -1 ) * (length(levels(inde_test$y)) -1 )
print("Critical Value of Previous_bool and Y Table:")
[1] "Critical Value of Previous_bool and Y Table:"
qchisq(0.95, df)
[1] 3.841459
print("Cramer.V of Previous_bool and Y:")
[1] "Cramer.V of Previous_bool and Y:"
previous_bool_v <- cramer.v(previous_bool_y)
previous_bool_v
[1] 0.2627237

“Previous_bool” and “y” is associated and the association strength is medium.

education

library(questionr)
print("Contingency Table of Education and Y:")
[1] "Contingency Table of Education and Y:"
education_y <- table(inde_test$education, inde_test$y)
education_y 
           
             no yes
  primary    67  52
  secondary 183 205
  tertiary  107 167
  unknown    16  25
print("Chi-Squared Test of Education and Y Table:")
[1] "Chi-Squared Test of Education and Y Table:"
chisq.test(education_y) 

    Pearson's Chi-squared test

data:  education_y
X-squared = 11.322, df = 3, p-value = 0.0101
df <- (length(levels(inde_test$education)) -1 ) * (length(levels(inde_test$y)) -1 )
print("Critical Value of Education and Y Table:")
[1] "Critical Value of Education and Y Table:"
qchisq(0.95, df)
[1] 7.814728
print("Cramer.V of Education and Y:")
[1] "Cramer.V of Education and Y:"
education_v <- cramer.v(education_y)
education_v
[1] 0.1173641

“education” and “y” is associated but the association strength is weak.

housing

library(questionr)
print("Contingency Table of Housing and Y:")
[1] "Contingency Table of Housing and Y:"
housing_y <- table(inde_test$housing, inde_test$y)
housing_y 
     
       no yes
  no  159 274
  yes 214 175
print("Chi-Squared Test of Housing and Y Table:")
[1] "Chi-Squared Test of Housing and Y Table:"
chisq.test(housing_y) 

    Pearson's Chi-squared test with Yates' continuity correction

data:  housing_y
X-squared = 26.929, df = 1, p-value = 0.000000211
df <- (length(levels(inde_test$housing)) -1 ) * (length(levels(inde_test$y)) -1 )
print("Critical Value of Housing and Y Table:")
[1] "Critical Value of Housing and Y Table:"
qchisq(0.95, df)
[1] 3.841459
print("Cramer.V of Housing and Y:")
[1] "Cramer.V of Housing and Y:"
housing_v <- cramer.v(housing_y)
housing_v
[1] 0.1834465

“housing” and “y” is associated and the association strength is medium.

library(questionr)
print("Contingency Table of Loan and Y:")
[1] "Contingency Table of Loan and Y:"
loan_y <- table(inde_test$loan, inde_test$y)
loan_y 
     
       no yes
  no  307 400
  yes  66  49
print("Chi-Squared Test of Loan and Y Table:")
[1] "Chi-Squared Test of Loan and Y Table:"
chisq.test(loan_y) 

    Pearson's Chi-squared test with Yates' continuity correction

data:  loan_y
X-squared = 7.2329, df = 1, p-value = 0.007158
df <- (length(levels(inde_test$loan)) -1 ) * (length(levels(inde_test$y)) -1 )
print("Critical Value of Loan and Y Table:")
[1] "Critical Value of Loan and Y Table:"
qchisq(0.95, df)
[1] 3.841459
print("Cramer.V of Loan and Y:")
[1] "Cramer.V of Loan and Y:"
loan_v <- cramer.v(loan_y)
loan_v
[1] 0.09732567

“loan” and “y” is weakly associated.

contact

library(questionr)
print("Contingency Table of Contact and Y:")
[1] "Contingency Table of Contact and Y:"
contact_y <- table(inde_test$contact, inde_test$y)
contact_y 
           
             no yes
  cellular  248 366
  telephone  22  39
  unknown   103  44
print("Chi-Squared Test of Contact and Y Table:")
[1] "Chi-Squared Test of Contact and Y Table:"
chisq.test(contact_y) 

    Pearson's Chi-squared test

data:  contact_y
X-squared = 44.449, df = 2, p-value = 0.0000000002229
df <- (length(levels(inde_test$contact)) -1 ) * (length(levels(inde_test$y)) -1 )
print("Critical Value of Contact and Y Table:")
[1] "Critical Value of Contact and Y Table:"
qchisq(0.95, df)
[1] 5.991465
print("Cramer.V of Contact and Y:")
[1] "Cramer.V of Contact and Y:"
contact_v <- cramer.v(contact_y)
contact_v
[1] 0.2325378

“contact” and “y” is strongly associated.

default

library(questionr)
print("Contingency Table of Default and Y:")
[1] "Contingency Table of Default and Y:"
default_y <- table(inde_test$default, inde_test$y)
default_y 
     
       no yes
  no  367 446
  yes   6   3
print("Chi-Squared Test of Contact and Y Table:")
[1] "Chi-Squared Test of Contact and Y Table:"
chisq.test(default_y) 
Chi-squared approximation may be incorrect

    Pearson's Chi-squared test with Yates' continuity correction

data:  default_y
X-squared = 0.90884, df = 1, p-value = 0.3404
df <- (length(levels(inde_test$default)) -1 ) * (length(levels(inde_test$y)) -1 )
print("Critical Value of Default and Y Table:")
[1] "Critical Value of Default and Y Table:"
qchisq(0.95, df)
[1] 3.841459
print("Cramer.V of Default and Y:")
[1] "Cramer.V of Default and Y:"
default_v <- cramer.v(default_y)
Chi-squared approximation may be incorrect
default_v
[1] 0.04499212

p-value is larger than 0.05, chi-squared value is smaller than critical value, so fail to reject the null hypothesis. We cannot conclude that “default” and “y” is associated.

marital

library(questionr)
print("Contingency Table of Marital and Y:")
[1] "Contingency Table of Marital and Y:"
marital_y <- table(inde_test$marital, inde_test$y)
marital_y 
          
            no yes
  divorced  41  52
  married  226 234
  single   106 163
print("Chi-Squared Test of Marital and Y Table:")
[1] "Chi-Squared Test of Marital and Y Table:"
chisq.test(marital_y) 

    Pearson's Chi-squared test

data:  marital_y
X-squared = 6.5475, df = 2, p-value = 0.03786
df <- (length(levels(inde_test$marital)) -1 ) * (length(levels(inde_test$y)) -1 )
print("Critical Value of Marital and Y Table:")
[1] "Critical Value of Marital and Y Table:"
qchisq(0.95, df)
[1] 5.991465
print("Cramer.V of Marital and Y:")
[1] "Cramer.V of Marital and Y:"
marital_v <- cramer.v(marital_y)
marital_v
[1] 0.08924855

No association between “marital” and “y”.

job and default

library(questionr)
print("Contingency Table of Default and Job:")
[1] "Contingency Table of Default and Job:"
default_job_new <- table(inde_test$default, inde_test$new_jobs)
default_job_new 
     
      admin. blue-collar management retired self-employed services technician Others
  no      96         151        178      59            27       72        124    106
  yes      0           1          2       0             1        0          1      4
print("Chi-Squared Test of Contact and Job Table:")
[1] "Chi-Squared Test of Contact and Job Table:"
chisq.test(default_job_new) 
Chi-squared approximation may be incorrect

    Pearson's Chi-squared test

data:  default_job_new
X-squared = 11.029, df = 7, p-value = 0.1374
df <- (length(levels(inde_test$default)) -1 ) * (length(levels(inde_test$new_jobs)) -1 )
print("Critical Value of Default and Job Table:")
[1] "Critical Value of Default and Job Table:"
qchisq(0.95, df)
[1] 14.06714
print("Cramer.V of Default and Job:")
[1] "Cramer.V of Default and Job:"
default_job_new_v <- cramer.v(default_job_new)
Chi-squared approximation may be incorrect
default_job_new_v
[1] 0.1158313

No association between job and default.

Visualize the results

var <- c("job", "poutcome", "month", "previous_bool", "education", "housing", "loan",
         "contact", "marital", "default")
v <- c(job_v, poutcome_v, month_v, previous_bool_v, education_v, housing_v, loan_v, contact_v, marital_v,
       default_v)
chi_test <- data.frame(var, v)
names(chi_test) <- c("Variables", "Cramer.V")
chi_test
library(ggplot2)
ggplot(data=chi_test, aes(x=reorder(Variables, -Cramer.V), y=Cramer.V, fill=Variables)) +
  geom_bar(stat="identity")+ 
  theme_classic() + ylab("Cramer's V") + xlab("Categorical Variables") +
  ggtitle("Cramer's V of Categorical Variables") +
  theme(plot.title = element_text(hjust = 0.5, size=14, face="bold"), 
        axis.text.x = element_text(angle = 45, hjust = 1, face="bold", size=14))

NA

Logistic Regression

Logistic Regression is for a continous varible and a categorical variable. Variables include: duration, age, balance, campaign, pdays, day

Between predictor variables: age and housing

  1. Build a logistic regression model
  2. Check the correlation estimate
  3. Compare the result and select the features together with the visualization summary

duration

lr_duration <- glm(y ~ duration, data = inde_test, family = "binomial")
summary(lr_duration)$coefficient
                Estimate   Std. Error   z value                             Pr(>|z|)
(Intercept) -1.251107855 0.1380794760 -9.060781 0.0000000000000000001295201367685781
duration     0.004172182 0.0003812472 10.943509 0.0000000000000000000000000007138219

age

lr_age <- glm(y ~ duration + age, data = inde_test, family = "binomial")
summary(lr_age)$coefficient
                Estimate   Std. Error    z value                             Pr(>|z|)
(Intercept) -1.370238978 0.3006851422 -4.5570558 0.0000051875640742799672725741461088
duration     0.004176776 0.0003817469 10.9412201 0.0000000000000000000000000007320799
age          0.002871328 0.0064213670  0.4471521 0.6547652432307282666101855284068733
lr <- glm(y ~ duration + age + balance + campaign + pdays + day, data = inde_test, family = "binomial")
summary(lr)

Call:
glm(formula = y ~ duration + age + balance + campaign + pdays + 
    day, family = "binomial", data = inde_test)

Deviance Residuals: 
    Min       1Q   Median       3Q      Max  
-3.7719  -0.8390   0.2247   0.8815   2.1955  

Coefficients:
               Estimate  Std. Error z value             Pr(>|z|)    
(Intercept) -1.13567078  0.35562163  -3.193              0.00141 ** 
duration     0.00433787  0.00039481  10.987 < 0.0000000000000002 ***
age         -0.00032327  0.00675359  -0.048              0.96182    
balance      0.00009650  0.00003147   3.067              0.00216 ** 
campaign    -0.14777000  0.04141840  -3.568              0.00036 ***
pdays        0.00445010  0.00082303   5.407         0.0000000641 ***
day         -0.01368338  0.00994092  -1.376              0.16868    
---
Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1

(Dispersion parameter for binomial family taken to be 1)

    Null deviance: 1132.5  on 821  degrees of freedom
Residual deviance:  866.1  on 815  degrees of freedom
AIC: 880.1

Number of Fisher Scoring iterations: 5
library(caret)
imp <- as.data.frame(varImp(lr))
imp <- data.frame(overall = imp$Overall,
           names   = rownames(imp))
lr_test <- imp[order(imp$overall,decreasing = T),]
names(lr_test) <- c("Importance", "Variables")
lr_test

One-Way ANOVA Test for Age and Y

“age” is normal distribution, so it can apply anova test.

oneway.test(age~ y, bank, var.equal=T)

    One-way analysis of means

data:  age and y
F = 98.55, num df = 1, denom df = 82232, p-value < 0.00000000000000022

Critical value

qf(0.95, 1, 82232)
[1] 3.841572

F value >= Critical Value, and p-value < 0.05, so we reject the null hypothesis. There is association between “age” and “y”.

Visualize the test results

library(ggplot2)
ggplot(data=lr_test, aes(x=reorder(Variables, -Importance), y=Importance, fill=Variables)) +
  geom_bar(stat="identity")+
  theme_classic() + ylab("Importance") + xlab("Continuous Variables") +
  ggtitle("Importance of Continous Variables") +
  theme(plot.title = element_text(hjust = 0.5, size=14, face="bold"), axis.text.x = element_text(
    face="bold", angle = 45, hjust = 1, size=14))

Feature Selection

feature_group1: month, poutcome, contact, duration feature_group2: month, poutcome, contact, duration, job, housing, previous_bool

Classification

bank_base <- bank

The order of level is by alphabet. “No” is the positive class by default. Change the order of the levels, so “yes” will be the positive class in the model.

bank_base$y = factor(bank_base$y,levels(bank_base$y)[c(2,1)])
levels(bank_base$y)
[1] "yes" "no" 

Now the “yes” will be the positive class in the models

Baseline Models

Two baseline classifiction models are created using decision tree and random forest with all the 16 variables.

## 75% of the sample size
size_base <- floor(0.75 * nrow(bank_base))
## set the seed to make your partition reproducible
set.seed(123)
# sample(seq_len(nrow(bank)), size = size1) -> the index of training set
train <- bank_base[sample(seq_len(nrow(bank_base)), size = size_base), ]
test <- bank_base[-sample(seq_len(nrow(bank_base)), size = size_base), ]

Create a decision tree

library(rpart)
base_dt <- rpart(y ~ ., train)
printcp(base_dt)

Classification tree:
rpart(formula = y ~ ., data = train)

Variables actually used in tree construction:
[1] contact  duration housing  month    poutcome

Root node error: 29962/61675 = 0.4858

n= 61675 

        CP nsplit rel error  xerror      xstd
1 0.428977      0   1.00000 1.00000 0.0041427
2 0.044056      1   0.57102 0.57156 0.0037120
3 0.022962      3   0.48291 0.48308 0.0035127
4 0.022128      4   0.45995 0.47176 0.0034838
5 0.019992      5   0.43782 0.43836 0.0033933
6 0.017088      6   0.41783 0.41846 0.0033357
7 0.010000      9   0.36656 0.36706 0.0031728
plotcp(base_dt)

#optimal cp for baseline tree
opt_index <- which.min(base_dt$cptable[, "xerror"])
cp_opt <- base_dt$cptable[opt_index, "CP"]
cp_opt
[1] 0.01
library(rpart.plot)
rpart.plot(base_dt)

Make prediction on the test set

test$BaseDT_Predict <- predict(base_dt, test[, 1:16], type = "class")

Compare the true y and the predicted y

head(test[, 17:18])

Expexrimental Models

Build models with the features selected. Feature group 1: month + poutcome + contact + duration; Feature group 2: month + poutcome + contact + duration + job + housing + previous_bool

Make a copy of bank. Create a new column “previous_bool” #18

bank_exp <- bank_base
#replace the orignal previous (numeric) with boolean
bank_exp$previous <- as.factor(bank_exp$previous>0)
head(bank_exp)

For the models use the feature group 1, the training set and test set are the same as the base line model. For the models use the feature group 2, use the training and test set below:

## 75% of the sample size
size_exp <- floor(0.75 * nrow(bank_exp))
## set the seed to make your partition reproducible
set.seed(123)
# sample(seq_len(nrow(bank)), size = size1) -> the index of training set
train_exp <- bank_exp[sample(seq_len(nrow(bank_exp)), size = size_exp), ]
test_exp <- bank_exp[-sample(seq_len(nrow(bank_exp)), size = size_exp), ]

The training set and test set are the same as in the baseline models because the seed is the same.

Decision Tree

cp ydefault =0.01

library(rpart)
# decision tree with feature group 1
tree1 <- rpart(y ~ month + poutcome + contact + duration, data = train)
# decision tree with feature group 2, the "previous" is bool, but with the same name
tree2 <- rpart(y ~ month + poutcome + contact + duration + job +
                  housing + previous, data = train_exp)

get the optimal cp based on cross-validation error

printcp(tree1)

Classification tree:
rpart(formula = y ~ month + poutcome + contact + duration, data = train)

Variables actually used in tree construction:
[1] contact  duration month    poutcome

Root node error: 29962/61675 = 0.4858

n= 61675 

        CP nsplit rel error  xerror      xstd
1 0.428977      0   1.00000 1.00000 0.0041427
2 0.044056      1   0.57102 0.57156 0.0037120
3 0.022962      3   0.48291 0.48308 0.0035127
4 0.022128      4   0.45995 0.47176 0.0034838
5 0.019992      5   0.43782 0.43836 0.0033933
6 0.010000      6   0.41783 0.41846 0.0033357
plotcp(tree1)

printcp(tree2)

Classification tree:
rpart(formula = y ~ month + poutcome + contact + duration + job + 
    housing + previous, data = train_exp)

Variables actually used in tree construction:
[1] contact  duration housing  month    poutcome

Root node error: 29962/61675 = 0.4858

n= 61675 

        CP nsplit rel error  xerror      xstd
1 0.428977      0   1.00000 1.00000 0.0041427
2 0.044056      1   0.57102 0.57152 0.0037120
3 0.022962      3   0.48291 0.48321 0.0035131
4 0.022128      4   0.45995 0.46796 0.0034739
5 0.019992      5   0.43782 0.43839 0.0033934
6 0.017088      6   0.41783 0.41840 0.0033355
7 0.010000      9   0.36656 0.36686 0.0031721

tree2 seems to be the same as baseline tree

plotcp(tree2)

#optimal cp for tree1
opt_index1 <- which.min(tree1$cptable[, "xerror"])
cp_opt1 <- tree1$cptable[opt_index1, "CP"]
#optimal cp for tree2
opt_index2 <- which.min(tree2$cptable[, "xerror"])
cp_opt2 <- tree2$cptable[opt_index2, "CP"]
cp_opt1
[1] 0.01
cp_opt2
[1] 0.01
library(rpart.plot)
rpart.plot(tree1)

rpart.plot(tree2)

Not all the input variables are used.

Make prediction on the test set

test$Tree1_Predict <- predict(tree1, test[, 1:16], type = "class")
test_exp$Tree2_Predict <- predict(tree2, test_exp[, 1:16], type = "class")

Compare the true y and the predicted y

head(test[, c(17, 19)])
head(test_exp[, c(17, 18)])

Random Forest

library(randomForest)
# random forest with feature group 1
forest1<- randomForest(y ~ month + poutcome + contact + duration, data = train)
library(randomForest)
# random forest with feature group 2
forest2<- randomForest(y ~ month + poutcome + contact + duration + job +
                  housing + previous, data = train_exp)

runs long!

Make prediction on test set

library(randomForest)
test$Forest1_Predict <- predict(forest1, test[, 1:16])
test_exp$Forest2_Predict <- predict(forest2, test_exp[, 1:16])

Evaluate the performance of the models

baseline decision tree

library(caret)
cm_basedt <- confusionMatrix(test$BaseDT_Predict, test$y)
cm_basedt
Confusion Matrix and Statistics

          Reference
Prediction  yes   no
       yes 9371 2479
       no  1156 7553
                                               
               Accuracy : 0.8232               
                 95% CI : (0.8179, 0.8284)     
    No Information Rate : 0.512                
    P-Value [Acc > NIR] : < 0.00000000000000022
                                               
                  Kappa : 0.6451               
 Mcnemar's Test P-Value : < 0.00000000000000022
                                               
            Sensitivity : 0.8902               
            Specificity : 0.7529               
         Pos Pred Value : 0.7908               
         Neg Pred Value : 0.8673               
             Prevalence : 0.5120               
         Detection Rate : 0.4558               
   Detection Prevalence : 0.5764               
      Balanced Accuracy : 0.8215               
                                               
       'Positive' Class : yes                  
                                               

experimental decision tree with feature group 1

library(caret)
cm_tree1<-confusionMatrix(test$Tree1_Predict, test$y)
cm_tree1
Confusion Matrix and Statistics

          Reference
Prediction  yes   no
       yes 9842 3534
       no   685 6498
                                               
               Accuracy : 0.7948               
                 95% CI : (0.7892, 0.8003)     
    No Information Rate : 0.512                
    P-Value [Acc > NIR] : < 0.00000000000000022
                                               
                  Kappa : 0.5866               
 Mcnemar's Test P-Value : < 0.00000000000000022
                                               
            Sensitivity : 0.9349               
            Specificity : 0.6477               
         Pos Pred Value : 0.7358               
         Neg Pred Value : 0.9046               
             Prevalence : 0.5120               
         Detection Rate : 0.4787               
   Detection Prevalence : 0.6506               
      Balanced Accuracy : 0.7913               
                                               
       'Positive' Class : yes                  
                                               

experimental decision tree with feature group 2

library(caret)
cm_tree2<-confusionMatrix(test_exp$Tree2_Predict, test_exp$y)
cm_tree2
Confusion Matrix and Statistics

          Reference
Prediction  yes   no
       yes 9371 2479
       no  1156 7553
                                               
               Accuracy : 0.8232               
                 95% CI : (0.8179, 0.8284)     
    No Information Rate : 0.512                
    P-Value [Acc > NIR] : < 0.00000000000000022
                                               
                  Kappa : 0.6451               
 Mcnemar's Test P-Value : < 0.00000000000000022
                                               
            Sensitivity : 0.8902               
            Specificity : 0.7529               
         Pos Pred Value : 0.7908               
         Neg Pred Value : 0.8673               
             Prevalence : 0.5120               
         Detection Rate : 0.4558               
   Detection Prevalence : 0.5764               
      Balanced Accuracy : 0.8215               
                                               
       'Positive' Class : yes                  
                                               

experimental random forest with feature group 1

library(caret)
cm_forest1<-confusionMatrix(test$Forest1_Predict, test$y)
cm_forest1
Confusion Matrix and Statistics

          Reference
Prediction  yes   no
       yes 9593 1758
       no   934 8274
                                               
               Accuracy : 0.8691               
                 95% CI : (0.8644, 0.8736)     
    No Information Rate : 0.512                
    P-Value [Acc > NIR] : < 0.00000000000000022
                                               
                  Kappa : 0.7375               
 Mcnemar's Test P-Value : < 0.00000000000000022
                                               
            Sensitivity : 0.9113               
            Specificity : 0.8248               
         Pos Pred Value : 0.8451               
         Neg Pred Value : 0.8986               
             Prevalence : 0.5120               
         Detection Rate : 0.4666               
   Detection Prevalence : 0.5521               
      Balanced Accuracy : 0.8680               
                                               
       'Positive' Class : yes                  
                                               

experimental random forest with feature group 2

library(caret)
cm_forest2<-confusionMatrix(test_exp$Forest2_Predict, test_exp$y)
cm_forest2
Confusion Matrix and Statistics

          Reference
Prediction  yes   no
       yes 9748 1584
       no   779 8448
                                               
               Accuracy : 0.8851               
                 95% CI : (0.8806, 0.8894)     
    No Information Rate : 0.512                
    P-Value [Acc > NIR] : < 0.00000000000000022
                                               
                  Kappa : 0.7696               
 Mcnemar's Test P-Value : < 0.00000000000000022
                                               
            Sensitivity : 0.9260               
            Specificity : 0.8421               
         Pos Pred Value : 0.8602               
         Neg Pred Value : 0.9156               
             Prevalence : 0.5120               
         Detection Rate : 0.4741               
   Detection Prevalence : 0.5512               
      Balanced Accuracy : 0.8841               
                                               
       'Positive' Class : yes                  
                                               
forest1$importance
         MeanDecreaseGini
month            4084.021
poutcome         2612.859
contact          1553.985
duration        11575.615
forest2$importance
         MeanDecreaseGini
month           3097.7082
poutcome        1755.3077
contact         1226.9245
duration        9517.5597
job              939.8455
housing          821.8555
previous         608.1455

ROC Curve

library('pROC')
Type 'citation("pROC")' for a citation.

Attaching package: ‘pROC’

The following objects are masked from ‘package:stats’:

    cov, smooth, var
plot(roc(test$y, predict(base_dt, test[,1:16], type = "prob")[,2]), col="darkgoldenrod1 ", legacy.axes=T, xlab="False Positive Rate (1-Specificity)", ylab="True Positive Rate (Sensitivity)", main="Compare Models")
plot(roc(test$y, predict(tree1, test[,1:16], type = "prob")[,2]), add=T, col="aquamarine")
plot(roc(test_exp$y, predict(tree2, test_exp[,1:16], type = "prob")[,2]), add=T, col="coral")
plot(roc(test$y, predict(forest1, test[,1:16], type = "prob")[,2]), add=T, col="chartreuse1")
plot(roc(test_exp$y, predict(forest2, test_exp[,1:16], type = "prob")[,2]), add=T, col="purple")
legend("bottomright", legend=c("Baseline Tree", "Tree1", "Tree2", "Forest1", "Forest2"),col=c("darkgoldenrod1", "aquamarine", "coral", "chartreuse1", "purple"), lwd=2)

Random Forest works better than Decision Tree. Feature group 2 is better than feature group 1. Decision tree with feature group 2 is the same as baseline decision tree. The two tree is exactly the same. Decision Tree with feature group 1 is slightly worse than baseline.

cm <- c(cm_basedt$byClass[1], cm_tree1$byClass[1], cm_tree2$byClass[1], cm_forest1$byClass[1],
        cm_forest2$byClass[1])
cm_df <- data.frame(cm)
cm_df$Model <- c("Baseline Tree", "Tree1", "Tree2", "Forest1", "Forest2")
colnames(cm_df)<- c("Sensitivity", "Model")
cm_df <- cm_df[, c(2,1)]
cm_df
library(ggplot2)
ggplot(data=cm_df, aes(x=reorder(Model, -Sensitivity), y=Sensitivity, fill=Model)) +
  geom_bar(stat="identity")+ 
  theme_classic() + ylab("Sensitivity") + xlab("Model") +
  ggtitle("Compare Model Sensitivity") +
  theme(plot.title = element_text(hjust = 0.5, size=14, face="bold"), 
        axis.text.x = element_text(angle = 45, hjust = 1, face="bold", size=14))

NA

ADD-ON: Create a baseline random forest

This will not go to report

default ntree=500

library(randomForest)
base_rf1<- randomForest(y ~ ., data = train)

Make prediction on the test set

test$BaseRF1_Predict <- predict(base_rf1, test[, 1:16], type = "class")

Compare the true y and the predicted y

head(test[, c(17,19)])

ntree = 500

library(caret)
cm_baserf <- confusionMatrix(test$BaseRF1_Predict, test$y)
cm_baserf
Confusion Matrix and Statistics

          Reference
Prediction   yes    no
       yes 10527   152
       no      0  9880
                                               
               Accuracy : 0.9926               
                 95% CI : (0.9913, 0.9937)     
    No Information Rate : 0.512                
    P-Value [Acc > NIR] : < 0.00000000000000022
                                               
                  Kappa : 0.9852               
 Mcnemar's Test P-Value : < 0.00000000000000022
                                               
            Sensitivity : 1.0000               
            Specificity : 0.9848               
         Pos Pred Value : 0.9858               
         Neg Pred Value : 1.0000               
             Prevalence : 0.5120               
         Detection Rate : 0.5120               
   Detection Prevalence : 0.5194               
      Balanced Accuracy : 0.9924               
                                               
       'Positive' Class : yes                  
                                               
LS0tCnRpdGxlOiAiQmFuayBNYXJrZXRpbmcgKEJhbGFuY2VkIERhdGEpIgpvdXRwdXQ6CiAgaHRtbF9ub3RlYm9vazogZGVmYXVsdAogIHBkZl9kb2N1bWVudDogZGVmYXVsdAotLS0KCiMgSW50cm9kdWN0aW9uCiMjIFByb2plY3QgT2JqZWN0aXZlClRoaXMgcHJvamVjdCBhaW1zIHRvIGJ1aWxkIGEgd29ya2FibGUgbW9kZWwgdG8gcHJlZGljdCBpZiBhIGNsaWVudCB3aWxsIHN1YnNjcmliZSBhIHRlcm0gZGVwb3NpdC4gS2V5IGZlYXR1cmVzIG9mIGNsaWVudHMgd2hvIGRpZCBzdWJzY3JpYmUgYSB0ZXJtIGRlcG9zaXQgd2lsbCBiZSBkaXNjb3ZlcmVkIGFuZCB1c2VkIGZvciBtYXJrZXRpbmcuIEZvciBleGFtcGxlLCB3aGF0IGFyZSB0aGUgcG90ZW50aWFsIGNsaWVudCBncm91cCBpbiB0ZXJtcyBvZiBhZ2UsIGpvYiwgcHJldmlvdXMgcmVsYXRpb25zaGlwIHdpdGggdGhlIGJhbmssIGV0Yy4gCgojIyBEYXRhIFByb2Nlc3NpbmcKVGhlIHByb2plY3Qgd29ya3Mgb24gdGhlIG9yaWdpbmFsIGRhdGEgc2V0IHdpdGggNDUyMTEgb2JzZXJ2YXRpb25zIGFuZCAxNyBhdHRyaWJ1dGVzLiBUaGUgZHJhd2JhY2sgb2YgdXNpbmcgdGhlIG9yaWdpbmFsIGRhdGEgc2V0IGlzOiB0aGUgZGF0YSBzZXQgaXMgdW5iYWxhbmNlZCBpbiB0aGUgY2xhc3MgbGFiZWwsIHdpdGggd2F5IG1vcmUgdGhhbiBubyB0aGFuIHllcy4gVGhpcyBnaXZlcyB0aGUgY2xhc3NpZmljYXRpb24gbW9kZWwgYSBnb29kIGFjY3VyYWN5IGJ1dCBhIGJhZCBzcGVjaWZpY2l0eSAodGhlIHJhdGUgY2FwdHVyaW5nIHRoZSB5ZXMgY2xhc3MuKSBUaHVzLCBfX2luIHRoaXMgcHJvamVjdCwgdGhlIG51bWJlciBvZiBvYnNlcnZhdGlvbiBvZiAieWVzIiBpbiB0aGUgInkiIGF0dHJpYnV0IHdpbGwgYmUgcmVwbGljYXRlZCBzbyB0aGF0IGl0IGVxdWFscyB0byB0aGUgbnVtYmVyIG9mIG9ic2VydmF0aW9ucyBvZiAibm8iIGluIHRoZSAieSIgYXR0cmlidXRlLl9fCgojIyBBYm91dCB0aGUgYXR0cmlidXRlcwpJbnB1dCB2YXJpYWJsZXM6CiAgICMgYmFuayBjbGllbnQgZGF0YToKICAgMSAtIGFnZSAobnVtZXJpYykKICAgMiAtIGpvYiA6IHR5cGUgb2Ygam9iIChjYXRlZ29yaWNhbDogImFkbWluLiIsInVua25vd24iLCJ1bmVtcGxveWVkIiwibWFuYWdlbWVudCIsImhvdXNlbWFpZCIsImVudHJlcHJlbmV1ciIsInN0dWRlbnQiLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAiYmx1ZS1jb2xsYXIiLCJzZWxmLWVtcGxveWVkIiwicmV0aXJlZCIsInRlY2huaWNpYW4iLCJzZXJ2aWNlcyIpIAogICAzIC0gbWFyaXRhbCA6IG1hcml0YWwgc3RhdHVzIChjYXRlZ29yaWNhbDogIm1hcnJpZWQiLCJkaXZvcmNlZCIsInNpbmdsZSI7IG5vdGU6ICJkaXZvcmNlZCIgbWVhbnMgZGl2b3JjZWQgb3Igd2lkb3dlZCkKICAgNCAtIGVkdWNhdGlvbiAoY2F0ZWdvcmljYWw6ICJ1bmtub3duIiwic2Vjb25kYXJ5IiwicHJpbWFyeSIsInRlcnRpYXJ5IikKICAgNSAtIGRlZmF1bHQ6IGhhcyBjcmVkaXQgaW4gZGVmYXVsdD8gKGJpbmFyeTogInllcyIsIm5vIikKICAgNiAtIGJhbGFuY2U6IGF2ZXJhZ2UgeWVhcmx5IGJhbGFuY2UsIGluIGV1cm9zIChudW1lcmljKSAKICAgNyAtIGhvdXNpbmc6IGhhcyBob3VzaW5nIGxvYW4/IChiaW5hcnk6ICJ5ZXMiLCJubyIpCiAgIDggLSBsb2FuOiBoYXMgcGVyc29uYWwgbG9hbj8gKGJpbmFyeTogInllcyIsIm5vIikKICAgIyByZWxhdGVkIHdpdGggdGhlIGxhc3QgY29udGFjdCBvZiB0aGUgY3VycmVudCBjYW1wYWlnbjoKICAgOSAtIGNvbnRhY3Q6IGNvbnRhY3QgY29tbXVuaWNhdGlvbiB0eXBlIChjYXRlZ29yaWNhbDogInVua25vd24iLCJ0ZWxlcGhvbmUiLCJjZWxsdWxhciIpIAogIDEwIC0gZGF5OiBsYXN0IGNvbnRhY3QgZGF5IG9mIHRoZSBtb250aCAobnVtZXJpYykKICAxMSAtIG1vbnRoOiBsYXN0IGNvbnRhY3QgbW9udGggb2YgeWVhciAoY2F0ZWdvcmljYWw6ICJqYW4iLCAiZmViIiwgIm1hciIsIC4uLiwgIm5vdiIsICJkZWMiKQogIDEyIC0gZHVyYXRpb246IGxhc3QgY29udGFjdCBkdXJhdGlvbiwgaW4gc2Vjb25kcyAobnVtZXJpYykKICAgIyBvdGhlciBhdHRyaWJ1dGVzOgogIDEzIC0gY2FtcGFpZ246IG51bWJlciBvZiBjb250YWN0cyBwZXJmb3JtZWQgZHVyaW5nIHRoaXMgY2FtcGFpZ24gYW5kIGZvciB0aGlzIGNsaWVudCAobnVtZXJpYywgaW5jbHVkZXMgbGFzdCBjb250YWN0KQogIDE0IC0gcGRheXM6IG51bWJlciBvZiBkYXlzIHRoYXQgcGFzc2VkIGJ5IGFmdGVyIHRoZSBjbGllbnQgd2FzIGxhc3QgY29udGFjdGVkIGZyb20gYSBwcmV2aW91cyBjYW1wYWlnbiAobnVtZXJpYywgLTEgbWVhbnMgY2xpZW50IHdhcyBub3QgcHJldmlvdXNseSBjb250YWN0ZWQpCiAgMTUgLSBwcmV2aW91czogbnVtYmVyIG9mIGNvbnRhY3RzIHBlcmZvcm1lZCBiZWZvcmUgdGhpcyBjYW1wYWlnbiBhbmQgZm9yIHRoaXMgY2xpZW50IChudW1lcmljKQogIDE2IC0gcG91dGNvbWU6IG91dGNvbWUgb2YgdGhlIHByZXZpb3VzIG1hcmtldGluZyBjYW1wYWlnbiAoY2F0ZWdvcmljYWw6ICJ1bmtub3duIiwib3RoZXIiLCJmYWlsdXJlIiwic3VjY2VzcyIpCgogIE91dHB1dCB2YXJpYWJsZSAoZGVzaXJlZCB0YXJnZXQpOgogIDE3IC0geSAtIGhhcyB0aGUgY2xpZW50IHN1YnNjcmliZWQgYSB0ZXJtIGRlcG9zaXQ/IChiaW5hcnk6ICJ5ZXMiLCJubyIpCgpGb3Igc291cmNlIGFuZCBhdHRyaWJ1dGUgaW5mbyBvZiB0aGUgZGF0YSBzZXQsIHBsZWFzZSByZWZlciB0byBodHRwczovL2FyY2hpdmUuaWNzLnVjaS5lZHUvbWwvZGF0YXNldHMvYmFuayttYXJrZXRpbmcKCgojIFVuZGVyc3RhbmQgdGhlIGRhdGEKCkltcG9ydCB0aGUgZGF0YSBzZXQKYGBge3J9CmxpYnJhcnkodGlkeXZlcnNlKQpiYW5rIDwtIHJlYWRfY3N2MigiL2hvbWUvanVueWFuMjYvREFUQS9CYW5rX01hcmtldGluZy9iYW5rL2JhbmstZnVsbC5jc3YiLCBjb2xfdHlwZXMgPSBjb2xzKCkpCmBgYAoKYGBge3J9Cm5yb3coYmFuaykKbmNvbChiYW5rKQpgYGAKCmBgYHtyfQpwcmludChoZWFkKGJhbmspKQpgYGAKCmBgYHtyfQpzYXBwbHkoYmFuaywgZnVuY3Rpb24oeCkgc3VtKGlzLm5hKHgpKSkKYGBgCgpUaGVyZSBpcyBubyBtaXNzaW5nIGRhdGEgaW4gdGhlIGRhdGEgc2V0LgoKX19NdWx0aXBseSB0aGUgbnVtYmVyIG9mICJ5ZXMiIG9ic2VydmF0aW9ucyBpbiB0aGUgInkiIGF0dHJpYnV0ZS5fX18KCmBgYHtyfQpsaWJyYXJ5KHNwbGl0c3RhY2tzaGFwZSkKYmFuayA8LSBhcy5kYXRhLmZyYW1lKChiYW5rKSkKbnVtX25vIDwtIG5yb3coYmFua1tiYW5rJHkgPT0gIm5vIiwgXSkKbnVtX3llcyA8LSBucm93KGJhbmtbYmFuayR5ID09ICJ5ZXMiLCBdKQpyZXBfbnVtIDwtIHJvdW5kKG51bV9uby9udW1feWVzKQpuZXdfeWVzX3Jvd3MgPC0gZXhwYW5kUm93cyhiYW5rW2JhbmskeSA9PSAieWVzIiwgXSwgY291bnQgPSByZXBfbnVtLCBjb3VudC5pcy5jb2wgPSBGQUxTRSkKcHJpbnQobnJvdyhuZXdfeWVzX3Jvd3MpKQpwcmludChudW1fbm8pCmBgYAoKYGBge3J9Cm5feWVzX25vIDwtIGRhdGEuZnJhbWUoYyhucm93KG5ld195ZXNfcm93cyksIG51bV9ubykpCm5feWVzX25vJGNsYXNzIDwtIGMoInllcyIsICJubyIpCmNvbG5hbWVzKG5feWVzX25vKSA8LSBjKCJjb3VudCIsICJjbGFzcyIpCm5feWVzX25vIDwtIG5feWVzX25vWywgYygyLCAxKV0Kbl95ZXNfbm8KYGBgCgoKYGBge3J9CmxpYnJhcnkoZ2dwbG90MikKZ2dwbG90KGRhdGE9bl95ZXNfbm8sIGFlcyh4PXJlb3JkZXIoY2xhc3MsIC1jb3VudCksIHk9Y291bnQsIGZpbGw9cmVvcmRlcihjbGFzcywgLWNvdW50KSkpICsKICBnZW9tX2JhcihzdGF0PSJpZGVudGl0eSIsIHdpZHRoPTAuNSkrIAogIHRoZW1lX2NsYXNzaWMoKSArIHlsYWIoIkNvdW50IikgKyB4bGFiKCJjbGFzc2VzIGluIHRhcmdldCB2YXJpYWJsZSBcInlcIiIpICsKICBnZ3RpdGxlKCJDb21wYXJlIHRoZSBOdW1iZXIgb2YgdGhlIENsYXNzZXMgKEFmdGVyIE92ZXIgU2FtcGxpbmcpIikgKwogIHRoZW1lKHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoaGp1c3QgPSAwLjUsIHNpemU9MTQsIGZhY2U9ImJvbGQiKSwgCiAgICAgICAgYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoaGp1c3QgPSAwLjUsIGZhY2U9ImJvbGQiLCBzaXplPTE0KSkrCiAgZ3VpZGVzKGZpbGw9RkFMU0UsIGNvbG9yPUZBTFNFKQpgYGAKCkFmdGVyIHRoZSByZXBsaWNhdGlvbiwgdGhlIGRhdGEgaXMgbW9yZSBiYWxhbmNlZC4gVGhlIG51bWJlciBvZiAieWVzIiByb3dzIGlzIDQyMzEyIGFuZCB0aGUgbnVtYmVyIG9mICJubyIgcm93cyBpcyAzOTkyMi4KCmBgYHtyfQpiYW5rIDwtIHJiaW5kKGJhbmtbYmFuayR5ID09ICJubyIsIF0sIG5ld195ZXNfcm93cykKc2VlZCA9IDMyMQpiYW5rIDwtIGJhbmtbc2FtcGxlKG5yb3coYmFuaykpLF0KbnJvdyhiYW5rKQpuY29sKGJhbmspCmBgYAoKVGhlIG5ldyBkYXRhIHNldCBoYXMgODIyMzQgcm93cyBhbmQgMTcgY29sdW1ucy4KCgpgYGB7cn0Kc3VtbWFyeShiYW5rKQpgYGAKCl9fVHVybiB0aGUgImNoYXJhY3RlciIgY2xhc3MgdmFyaWFibGVzIGludG8gImZhY3RvciIuX18KCmBgYHtyfQpzYXBwbHkoYmFuaywgY2xhc3MpCmBgYAoKYGBge3J9CmMgPC0gY29sbmFtZXMoZHBseXI6OnNlbGVjdF9pZihiYW5rLCBpcy5jaGFyYWN0ZXIpKQpiYW5rW2NdIDwtIGxhcHBseShiYW5rW2NdLCBmYWN0b3IpCnNhcHBseShiYW5rLCBjbGFzcykKYGBgCgoKIyBJbXByb3ZlZCBNb2RlbAoKIyMgRmVhdHVyZSBFbmdpbmVlcmluZwoKMS4gR2VuZXJhdGUgYSBjb3JyZWxhdGlvbiBoZWF0bWFwIGFuZCBwYWlyLXdpc2Ugc2NhdHRlciBwbG90IHRvIHZpc3VhbGl6ZSB0aGUgYmlnIHBpY3R1cmUgb2YgdGhlIGRhdGEuIFxuCjIuIFZpc3VhbGl6ZSBlYWNoIHZhcmlhYmxlIGFuZCBzZWUgaXRzIGFzc29jaWF0aW9pbiB3aXRoIHRoZSBvdXRwdXQgdmFyaWFibGUgInkiLiBcbgozLiBWaXN1YWxpemUgdGhvc2UgcHJlZGljdG9yIHZhcmlhYmxlcyB0aGF0IHNlZW0gdG8gYmUgYXNzb2NpYXRlZC4gXG4KNC4gU3VtbWVyaXplIHRoZSByZXN1bHQgXG4KCl9fVHVybiBjYXRlZ29yaWNhbCB2YXJpYWJsZXMgaW50byBudW1lcmljYWwuX18KCmBgYHtyfQpiYW5rX251bWVyaWMgPC0gYmFuawpzYXBwbHkoYmFua19udW1lcmljLCBjbGFzcykKYGBgCgpgYGB7cn0KbXVzdF9jb252ZXJ0IDwtIHNhcHBseShiYW5rX251bWVyaWMsaXMuZmFjdG9yKQpiYW5rX251bWVyaWNfdGVtcCA8LSBzYXBwbHkoYmFua19udW1lcmljWyxtdXN0X2NvbnZlcnRdLHVuY2xhc3MpCmJhbmtfbnVtZXJpYyA8LSBjYmluZChiYW5rX251bWVyaWNbLCFtdXN0X2NvbnZlcnRdLGJhbmtfbnVtZXJpY190ZW1wKQoKI1Jlb3JkZXIgdGhlIGNvbHVtbiBuYW1lcy4gTWFrZSB0aGVtIGluIHN5bmMgd2l0aCBiYW5rLgpjbmFtZXMgPSBjb2xuYW1lcyhiYW5rKQpiYW5rX251bWVyaWMgPC0gYmFua19udW1lcmljWywgY25hbWVzXQpoZWFkKGJhbmtfbnVtZXJpYykKYGBgCgoiYmFua19udW1lcmljIiBpcyBhIGRhdGFzZXQgZ2VuZXJhdGVkIGZyb20gImJhbmsiIGFuZCB3aXRoIGFsbCBudW1lcmljYWwgZGF0YS4KCl9fRXhhbWluZSB0aGUgY29ycmVsYXRpb24gYW5kIHNpZ25pZmljYW50IGxldmVsKHB2YWx1ZSkgYmV0d2VlbiB2YXJpYWJsZXMuX18KCmBgYHtyfQpsaWJyYXJ5KEhtaXNjKQpvcHRpb25zKHNjaXBlbj05OTkpCmNvcl9wIDwtIHJjb3JyKHggPSBkYXRhLm1hdHJpeChiYW5rX251bWVyaWMpWywgMToxNl0sIHkgPSBiYW5rX251bWVyaWNbLCAxN10sICB0eXBlID0gYygicGVhcnNvbiIsICJzcGVhcm1hbiIpKQpgYGAKCkNvcnJlbGF0aW9uIE1hdHJ4OgoKYGBge3J9CnJvdW5kKGNvcl9wJHIsIDIpCmBgYAoKCmBgYHtyfQpyb3VuZChjb3JfcCRQLCAyKQpgYGAKCmBgYHtyfQojaW5zdGFsbC5wYWNrYWdlcygiY29ycnBsb3QiKQpsaWJyYXJ5KGNvcnJwbG90KQpNIDwtIGNvcl9wJHIKcF9tYXQgPC0gY29yX3AkUApjb3JycGxvdChNLCB0eXBlID0gInVwcGVyIixjb2wgPSBoZWF0LmNvbG9ycygxMDApLGJnID0gImRhcmtibHVlIiwKICAgICAgICAgcC5tYXQgPSBwX21hdCwgc2lnLmxldmVsID0gMC4wNSkKYGBgCgpfX1RoZSBjb3JyZWxhdGlvbiBtYXRyaXggaXMgdXNpbmcgUGVhcnNvbiBDb3JyZWxhdGlvbiBhbmQgU3BlYXJtYW4ncyBSYW5rIENvcnJlbGF0aW9uLCB0aGUgZm9ybWFlciBvZiB3aGljaCBkZXNpZ25lZCBmb3IgcmF0aW8gYW5kIGludGVydmVsIGRhdGEsIHdoaWxlIHRoZSBsYXR0ZXIgb2Ygd2hpY2ggZm9yIG9yZGluYWwsIHJhdGlvIGFuZCBpbnRlcnZhbC4gSXQgaXMgbm90IHZlcnkgYWNjdXJheSBiZWNhdXNlIHRoZSBub3QgYWxsIHRoZSB2YXJpYWJsZXMgYXJlIG9mIHRoZXNlIGRhdGEgdHlwZS4gU3RpbGwgLCBpdCBjYW4gYmUgYSByZWZlcmVuY2Ugb2YgaG93IHRoZSBkYXRhIHNlZW0gdG8gYmUgY29ycmVsYXRlZC4KCkluIHRoZSBtYWpvcml0eSBvZiBhbmFseXNlcywgYW4gYWxwaGEgb2YgMC4wNSBpcyB1c2VkIGFzIHRoZSBjdXRvZmYgZm9yIHNpZ25pZmljYW5jZS4gSWYgdGhlIHAtdmFsdWUgaXMgbGVzcyB0aGFuIDAuMDUsIHdlIHJlamVjdCB0aGUgbnVsbCBoeXBvdGhlc2lzIHRoYXQgdGhlcmUncyBubyBjb3JyZWxhdGlvbiBiZXR3ZWVuIHRoZSB2YXJpYWJsZXMgYW5kIGNvbmNsdWRlIHRoYXQgYSBzaWduaWZpY2FudCBjb3JyZWxhdGlvbiBkb2VzIGV4aXN0LiBJZiB0aGUgcC12YWx1ZSBpcyBsYXJnZXIgdGhhbiAwLjA1LCB3ZSBDQU5OT1QgcmVqZWN0IHRoZSBudWxsIGh5cG90aGVzZSBhbmQgQ0FOTk9UIGNvbmNsdWRlIHRoYXQgYSBzaWduaWZpY2FudCBjb3JyZWxhdGlvbiBleGlzdHMuIAoKVGhlIGNyb3NzZXMgaW4gdGhlIHBsb3QgaW5kaWNhdGUgcC12YWx1ZSBoaWdoZXIgdGhhbiAwLjA1LCB3aGljaCBtZWFucyB3ZSBjYW5ub3QgY29uY2x1ZGUgdGhhdCB0aGUgY29ycmVsYXRpb24gYmV0d2VlbiB0aGUgdmFyaWJhbGVzIGlzIHNpZ25pZmljYW50LiBGb3IgdGhlIHJlc3Qgb2YgdGhlbSwgd2UgY2FuIGNvbmNsdWRlIHRoYXQgdGhlIGNvcnJhbGF0aW9uIGlzIHNpZ25pZmljYW50LgoKVGh1cywgYWxsIHZhcmlhYmxlcyBoYXMgc2lnbmlmaWNhbnQgY29ycmVsYXRpb24gd2l0aCB0aGUgInkiLCBhbHRob3VnaCB0aGUgY29ycmVsYXRpb24gY29lZmZpY2llbnRzIGFyZSBub3QgaGlnaC4gCgpUaGUgY29ycmVsYXRpb24gYmV0d2VlbiAicGRheXMiIGFuZCAicG91dGNvbWUiIGlzIG5lZ2F0aXZlbHkgc3Ryb25nLCB3aGljaCBuZWVkIHRvIGxvb2sgY2xvc2VyIGxhdGVyLiAKCgpfX1NjYXR0ZXJwbG90IE1hdHJpeF9fCgpUaGUgcG9pbnRzIGluIGEgc2NhdHRlcnBsb3QgbWF0cml4IGNhbiBiZSBjb2xvcmVkIGJ5IHRoZSBjbGFzcyBsYWJlbCBpbiBjbGFzc2lmaWNhdGlvbiBwcm9ibGVtcy4gVGhpcyBjYW4gaGVscCB0byBzcG90IGNsZWFyIChvciB1bmNsZWFyKSBzZXBhcmF0aW9uIG9mIGNsYXNzZXMgYW5kIHBlcmhhcHMgZ2l2ZSBhbiBpZGVhIG9mIGhvdyBkaWZmaWN1bHQgdGhlIHByb2JsZW0gbWF5LgoKYGBge3IsIGZpZy5oZWlnaHQ9MzAsIGZpZy53aWR0aD0zMH0KcGFpcnMoeX4uLCBkYXRhID0gYXMubWF0cml4KGJhbmspLCBjb2wgPSBiYW5rJHksIGxvd2VyLnBhbmVsID0gTlVMTCkKYGBgCgpUaGUgcGFpciBwbG90IHNob3dzIGhvdyB0aGUgdmFyaWFibGUgcGFpcnMgY2FuIHNlcGVyYXRlIHRoZSAieSIgdmFyaWFibGUuIE92ZXJhbGwsIHRoZSBzZXBlcmF0aW9uIGlzIG5vdCB2ZXJ5IGNsZWFyLiBDYXRlZ29yaWNhbCBhbmQgbnVtZXJpY2FsIHZhcmlhYmxlcyBhcmUgYWxzbyBpbmNsdWRlZCwgd2hpY2ggbWFrZXMgdGhlIHBsb3QgdW5uZWNlc3NhcmlseSBiaWcuCgpMZXZlbHMgb2YgdGhlIGZhY3RvciB2YXJpYWJsZXM6CgpgYGB7cn0Kc2FwcGx5KGRwbHlyOjpzZWxlY3RfaWYoYmFuaywgaXMuZmFjdG9yKSwgbGV2ZWxzKQpgYGAKClN1bW1hcnkgb2YgbnVtZXJpYyB2YXJpYWJsZToKCmBgYHtyfQpzYXBwbHkoZHBseXI6OnNlbGVjdF9pZihiYW5rLCBpcy5udW1lcmljKSwgc3VtbWFyeSkKYGBgCgpGcm9tIHN1bW1hcnksIHdlIGNhbiBzZWUgImFnZSIgc2VlbXMgbm9ybWFsbHkgZGlzdHJpYnV0ZWQuICJiYWxhbmNlIiBpcyB2ZXJ5IHdpZGVseSBzcHJlYWQuICJkYXkiIGl0c2VsZiBkb2Vzbid0IG1ha2UgYSBsb3Qgb2Ygc2Vuc2UgYW5kIG1heSBiZSBjb21iaW5lZCB3aXRoICJtb250aCIuICJkdXJhdGlvbiIsICJjYW1wYWlnbiIsICJwZGF5cyIgYW5kICJwcmV2aW91cyIgc2VlbSB0byBiZSBza2V3ZWQuCgpfX1Zpc3VhbGl6YXRpb24gb2YgdGhlIERhdGEgU2V0X18KCmBgYHtyfQpnZ3Bsb3QoYmFuaywgYWVzKHg9YWdlLCBmaWxsPXksIGNvbG9yPXkpKSsKICBnZW9tX2hpc3RvZ3JhbShwb3NpdGlvbj0iaWRlbnRpdHkiLCBhbHBoYT0wLjUsIGJpbndpZHRoPTIpKwogIHN0YXRfZnVuY3Rpb24oZnVuID0gZnVuY3Rpb24oYmFuaywgbWVhbiwgc2QsIG4pewogICAgbiAqIGRub3JtKHggPSBiYW5rLCBtZWFuID0gbWVhbiwgc2QgPSBzZCkgfSwgCiAgICBhcmdzID0gd2l0aChiYW5rLCBjKG1lYW4gPSBtZWFuKGFnZSksIHNkID0gc2QoYWdlKSwgbiAKICAgICAgICAgICAgICAgICAgICAgICAgPSBsZW5ndGgoYWdlKSkpLCBjb2w9ImJsdWUiKSArCiAgc2NhbGVfY29sb3JfbWFudWFsKHZhbHVlcz1jKCJsaWdodGJsdWUiLCAiY29yYWwiKSkrCiAgc2NhbGVfZmlsbF9tYW51YWwodmFsdWVzPWMoImxpZ2h0Ymx1ZSIsICJjb3JhbCIpKSArCiAgdGhlbWVfY2xhc3NpYygpICsKICBnZ3RpdGxlKCJBZ2UgRGlzdHJpYnV0aW9uIENvbG9yZWQgYnkgWSIpICsKICB0aGVtZShwbG90LnRpdGxlID0gZWxlbWVudF90ZXh0KGhqdXN0ID0gMC41LCBzaXplPTE0LCBmYWNlPSJib2xkIikpCmBgYAoKClJlZCBhbmQgYmx1ZSBhcmVhIHNlcGVyYXRlZCBhIGxpdHRsZSwgYnV0IG92ZXJhbCwgaXQgaXMgb3ZlcmxhcC4gQWdlIGlzIG5vbWFsbHkgZGlzdHJpYnV0ZWQuCgoKYGBge3J9CnNwaW5lcGxvdCh4ID0gYmFuayRhZ2UsIHkgPSBiYW5rJHksIHhsYWIgPSAiYWdlIiwgeWxhYiA9ICJ5IiwgYnJlYWtzPTIwLAogICAgICAgICAgbWFpbiA9ICJBZ2UgdnMgWSIsIGNvbCA9IGMoImxpZ2h0Ymx1ZSIsICAiY29yYWwiKSkKYGBgCgpNb3JlIHRoYW4gODAlIG9mIHBlb3BsZSBhZ2VkIG9sZGVyIHRoYW4gNjAgYW5kIHlvdW5nZXIgdGhhbiAxOCBzdWJzcmliZSBhIHRlcm0gZGVwb3NpdC4gQnV0IHRoaXMgdHdvIGdyb3VwcyBhcmUgb25seSBzbWFsbCBudW1iZXIgb2YgcGVvcGxlLgoKCmBgYHtyfQpnZ3Bsb3QoYmFuaywgYWVzKHg9YmFsYW5jZSwgZmlsbD15LCBjb2xvcj15KSkrCiAgZ2VvbV9oaXN0b2dyYW0ocG9zaXRpb249ImlkZW50aXR5IiwgYWxwaGE9MC41LCBiaW53aWR0aD0yMDAwKSsKICBzY2FsZV9jb2xvcl9tYW51YWwodmFsdWVzPWMoImxpZ2h0Ymx1ZSIsICJjb3JhbCIpKSsKICBzY2FsZV9maWxsX21hbnVhbCh2YWx1ZXM9YygibGlnaHRibHVlIiwgImNvcmFsIikpICsKICB0aGVtZV9jbGFzc2ljKCkgKwogIGdndGl0bGUoIkJhbGFuY2UgRGlzdHJpYnV0aW9uIENvbG9yZWQgYnkgWSIpICsKICB0aGVtZShwbG90LnRpdGxlID0gZWxlbWVudF90ZXh0KGhqdXN0ID0gMC41LCBzaXplPTE0LCBmYWNlPSJib2xkIikpCmBgYAoKImJhbGFuY2UiIGlzIG5vdCBub3JtYWxseSBkaXN0cmlidXRlZC4gVGhlIHR3byBjb2xvciBoYXMgYSBsaXR0bGUgc2VwZXJhdGlvbi4KCmBgYHtyfQpzcGluZXBsb3QoeCA9IGJhbmskYmFsYW5jZSwgeSA9IGJhbmskeSwgeGxhYiA9ICJiYWxhbmNlIiwgeWxhYiA9ICJ5IiwgYnJlYWtzPTEwMCwKICAgICAgICAgIG1haW4gPSAiYmFsYW5jZSB2cyB5IiwgY29sID0gYygibGlnaHRibHVlIiwgICJjb3JhbCIpKQpgYGAKCmBgYHtyfQpzcGluZXBsb3QoeCA9IGJhbmtbYmFuayRiYWxhbmNlID4gMjUwMDAsICJiYWxhbmNlIl0sIHkgPSBiYW5rW2JhbmskYmFsYW5jZSA+IDI1MDAwLCAieSJdLCB4bGFiID0gImJhbGFuY2UiLCB5bGFiID0gInkiLCBicmVha3M9MTAwLAogICAgICAgICAgbWFpbiA9ICJiYWxhbmNlICg+MjUwMDApIHZzIHkiLCBjb2wgPSBjKCJsaWdodGJsdWUiLCAgImNvcmFsIikpCmBgYAoKQmFsYW5jZSBsYXJnZXIgdGhhbiAyNTAwMCBoYXMgbW9yZSAieWVzIi4KCmBgYHtyfQpiYW5rW2JhbmskYmFsYW5jZSA+IDY1MDAwLCBjKCJiYWxhbmNlIiwgInkiKV0KYGBgCgpgYGB7cn0Kc3BpbmVwbG90KHggPSBiYW5rW2JhbmskYmFsYW5jZSA8IC02MDAsICJiYWxhbmNlIl0sIHkgPSBiYW5rW2JhbmskYmFsYW5jZSA8IC02MDAsICJ5Il0sIHhsYWIgPSAiYmFsYW5jZSIsIHlsYWIgPSAieSIsIGJyZWFrcz0xMDAsCiAgICAgICAgICBtYWluID0gImJhbGFuY2UgKDwwKSB2cyB5IiwgY29sID0gYygibGlnaHRibHVlIiwgICJjb3JhbCIpKQpgYGAKCkl0IHNlZW1zIGxpa2UgZm9yIGJhbGFuY2UgbW9yZSB0aGFuIDI1MDAwLCB0aGUgbW9yZSBiYWxhbmNlLCB0aGUgaGlnaGVyIHByb3BvcnRpb24gb2YgcGVvcGxlIHN1YnNyaWJlIGEgdGVybSBkZXBvc2l0LiBCdXQgdGhpcyBpcyB2ZXJ5IHNtYWxsIG51bWJlciBvZiBncm91cC4gRm9yIG5lZ2F0aXZlIGJhbGFuY2UsIHRoZXJlIGFyZSBsZXNzIHBlb3BsZSBzdWJzY3JpYmUuCgpQbG90IGFnZSwgYmFsYW5jZSBhbmQgeQoKYGBge3J9CiNsaWJyYXJ5KGdncGxvdDIpCmxpYnJhcnkoZ2dwdWJyKQojIEdyb3VwZWQgU2NhdHRlciBwbG90IHdpdGggbWFyZ2luYWwgZGVuc2l0eSBwbG90cwpnZ3NjYXR0ZXJoaXN0KAogIGJhbmtbLCBjKCJhZ2UiLCAiYmFsYW5jZSIsICJ5IildLCB4ID0gImFnZSIsIHkgPSAiYmFsYW5jZSIsCiAgY29sb3IgPSAieSIsIHNpemUgPSAzLCBhbHBoYSA9IDAuNiwKICBwYWxldHRlID0gYygibGlnaHRibHVlIiwgImNvcmFsIiksIGxlZ2VuZCA9ICJyaWdodCIpCgpgYGAKClRoZSB0d28gY29sb3JzIGFyZSBhbG1vc3Qgd2VsbCBtb2l4LiAiYWdlIiBhbmQgImJhbGFuY2UiIHRvZ2V0aGVyIGRvbid0IHNlcGVyYXRlICJ5ZXMiIGFuZCAibm8iIGluICJ5IiB3ZWxsLgoKCmBgYHtyfQpnZ3Bsb3QoYmFuaywgYWVzKHg9ZHVyYXRpb24sIGZpbGw9eSwgY29sb3I9eSkpKwogIGdlb21faGlzdG9ncmFtKHBvc2l0aW9uPSJpZGVudGl0eSIsIGFscGhhPTAuNSwgYmlud2lkdGg9MTAwKSsKICBzY2FsZV9jb2xvcl9tYW51YWwodmFsdWVzPWMoImxpZ2h0Ymx1ZSIsICJjb3JhbCIpKSsKICBzY2FsZV9maWxsX21hbnVhbCh2YWx1ZXM9YygibGlnaHRibHVlIiwgImNvcmFsIikpICsKICB0aGVtZV9jbGFzc2ljKCkgKwogIGdndGl0bGUoIkR1cmF0aW9uIERpc3RyaWJ1dGlvbiBDb2xvcmVkIGJ5IFkiKSArCiAgdGhlbWUocGxvdC50aXRsZSA9IGVsZW1lbnRfdGV4dChoanVzdCA9IDAuNSwgc2l6ZT0xNCwgZmFjZT0iYm9sZCIpKSsKICB4bGFiKCJkdXJhdGlvbjogbGFzdCBjb250YWN0IGR1cmF0aW9uLCBpbiBzZWNvbmRzIikKYGBgCgpUd28gY29sb3JzIGhhdmUgYSBvYnZpb3VzIHNlcGVyYXRpb24uICJkdXJhdGlvbiIgc2VwZXJhdGVzIHRoZSB0d28gY2F0ZWdvcmllcyBvZiAieSIgcHJldHR5IHdlbGwuCgpgYGB7cn0Kc3BpbmVwbG90KHggPSBiYW5rJGR1cmF0aW9uLCB5ID0gYmFuayR5LCB4bGFiID0gImR1cmF0aW9uOiBsYXN0IGNvbnRhY3QgZHVyYXRpb24sIGluIHNlY29uZHMiLCB5bGFiID0gInkiLCBicmVha3M9MTAwLAogICAgICAgICAgbWFpbiA9ICJEdXJhdGlvbiB2cyBZIiwgY29sID0gYygibGlnaHRibHVlIiwgICJjb3JhbCIpKQpgYGAKCiJkdXJhdGlvbiIgYW5kICJ5ImFyZSBwcmV0dHkgc3Ryb25nbHkgYXNzb2NpYXRlZC4gVGhlIGxvbmdlciBkdXJhdGlvbiBpcywgdGhlIGJpZ2dlciBwcnBvcnRpb24gb2YgcGVvcGxlIHN1YnNjaWJlIGEgdGVybSBkZXBvc2l0LgoKCmBgYHtyfQpzdW1tYXJ5KGJhbmskZHVyYXRpb24pCmBgYAoKYGBge3J9CmdncGxvdChiYW5rLCBhZXMoeD1jYW1wYWlnbiwgZmlsbD15LCBjb2xvcj15KSkrCiAgZ2VvbV9oaXN0b2dyYW0ocG9zaXRpb249ImlkZW50aXR5IiwgYWxwaGE9MC41LCBiaW53aWR0aD0xKSsKICBzY2FsZV9jb2xvcl9tYW51YWwodmFsdWVzPWMoImxpZ2h0Ymx1ZSIsICJjb3JhbCIpKSsKICBzY2FsZV9maWxsX21hbnVhbCh2YWx1ZXM9YygibGlnaHRibHVlIiwgImNvcmFsIikpICsKICB0aGVtZV9jbGFzc2ljKCkgKwogIGdndGl0bGUoIkNhbXBhaWduIERpc3RyaWJ1dGlvbiBDb2xvcmVkIGJ5IFkiKSArCiAgdGhlbWUocGxvdC50aXRsZSA9IGVsZW1lbnRfdGV4dChoanVzdCA9IDAuNSwgc2l6ZT0xNCwgZmFjZT0iYm9sZCIpKSArCiAgeGxhYigiY2FtcGFpZ246IG51bWJlciBvZiBjb250YWN0cyBwZXJmb3JtZWQgZHVyaW5nIHRoaXMgY2FtcGFpZ24iKQpgYGAKClRoZSB0d28gY29sb3JzIG92ZXJsYXAgd2VsbCBpbiB0aGUgbWlkZGxlIGFuZCBoYXZlIHNvbWUgc2VwZXJhdGlvbiBhdCB0aGUgdHdvIGVuZHMuCgpgYGB7cn0Kc3VtbWFyeShiYW5rJGNhbXBhaWduKQpgYGAKClVuaXF1ZSBudW1iZXIgaW4gImNhbXBhaWduIjoKCmBgYHtyfQpzb3J0KHVuaXF1ZShiYW5rJGNhbXBhaWduKSkKYGBgCgoKYGBge3J9CmFnZ3JlZ2F0ZShkYXRhLmZyYW1lKGNvdW50ID0gYmFuayRjYW1wYWlnbiksIGxpc3QodmFsdWUgPSBiYW5rJGNhbXBhaWduKSwgbGVuZ3RoKQpgYGAKCk1vc3Qgb2YgdGhlIGNhbXBhaWduIGlzIG9uIDEgYW5kIDIuCgpgYGB7cn0Kc3BpbmVwbG90KHggPSBiYW5rJGNhbXBhaWduLCB5ID0gYmFuayR5LCB4bGFiID0gImNhbXBhaWduOiBudW1iZXIgb2YgY29udGFjdHMgcGVyZm9ybWVkIGR1cmluZyB0aGlzIGNhbXBhaWduIiwgeWxhYiA9ICJ5IiwgYnJlYWtzPTUwLAogICAgICAgICAgbWFpbiA9ICJDYW1wYWlnbiB2cyBZIiwgY29sID0gYygibGlnaHRibHVlIiwgICJjb3JhbCIpKQpgYGAKClNlZW0gdG8gaGF2ZSBzb21lIHRyZW5kLgoKYGBge3J9CmRmX2NhbSA8LSBiYW5rW2JhbmskY2FtcGFpZ24gPiAzLCBdCnNwaW5lcGxvdCh4ID0gZGZfY2FtJGNhbXBhaWduLCB5ID0gZGZfY2FtJHksIHhsYWIgPSAiY2FtcGFpZ246IG51bWJlciBvZiBjb250YWN0cyBwZXJmb3JtZWQgZHVyaW5nIHRoaXMgY2FtcGFpZ24iLCB5bGFiID0gInkiLCBicmVha3M9NTAsIG1haW4gPSAiQ2FtcGFpZ24gKD4zKSB2cyBZIiwgY29sID0gYygibGlnaHRibHVlIiwgICJjb3JhbCIpKQpgYGAKClRoZXJlIGlzIGEgdHJlbmQgdGhhdCB0aGUgbW9yZSBudW1iZXIgb2YgY2FtcGFpZ24sIHRoZSBsZXNzIHBlcmNlbnRhZ2Ugb2YgY2xpZW50cyBzdWJzdHJpYmUgYSB0ZXJtIGRlcG9zaXQsIEV4cGVjaWFsbHkgZm9yIGNhbXBhaWduIG1vcmUgdGhhbiAzLgoKCmBgYHtyfQpsaWJyYXJ5KGdncHVicikKIyBHcm91cGVkIFNjYXR0ZXIgcGxvdCB3aXRoIG1hcmdpbmFsIGRlbnNpdHkgcGxvdHMKZ2dzY2F0dGVyaGlzdCgKICBiYW5rWywgYygiY2FtcGFpZ24iLCAiZHVyYXRpb24iLCAieSIpXSwgeCA9ICJjYW1wYWlnbiIsIHkgPSAiZHVyYXRpb24iLAogIGNvbG9yID0gInkiLCBzaXplID0gMywgYWxwaGEgPSAwLjYsCiAgcGFsZXR0ZSA9IGMoImxpZ2h0Ymx1ZSIsICJjb3JhbCIpLCBsZWdlbmQgPSAicmlnaHQiKSsKICBnZ3RpdGxlKCJDYW1wYWlnbiBhbmQgRHVyYXRpb24gQ29sb3JlZCBieSBZIikgKwogIHRoZW1lKHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoaGp1c3QgPSAwLjUsIHNpemU9MTQsIGZhY2U9ImJvbGQiKSkKYGBgCgpUaGUgdHdvIGNvbG9ycyBzZXBlcmF0ZWQuIE1heSBiZSBiZWNhdXNlICJkdXJhdGlvbiIgaXMgYSBwcmVkaWN0b3IuIEJ1dCAiY2FtcGFpZ24iIHNob3VsZCBiZSBrZXB0IGFzIHdlbGwuCgpgYGB7cn0Kc3VtbWFyeShiYW5rJHByZXZpb3VzKQpgYGAKClVuaXF1ZSBudW1iZXIgaW4gInByZXZpb3VzIjoKCmBgYHtyfQpzb3J0KHVuaXF1ZShiYW5rJHByZXZpb3VzKSkKYGBgCgoKYGBge3J9CmFnZ3JlZ2F0ZShkYXRhLmZyYW1lKGNvdW50ID0gYmFuayRwcmV2aW91cyksIGxpc3QodmFsdWUgPSBiYW5rJHByZXZpb3VzKSwgbGVuZ3RoKQpgYGAKCmBgYHtyfQpsaWJyYXJ5KGdncGxvdDIpCmdncGxvdChiYW5rLCBhZXMoeD1wcmV2aW91cywgZmlsbD15LCBjb2xvcj15KSkrCiAgZ2VvbV9oaXN0b2dyYW0ocG9zaXRpb249ImlkZW50aXR5IiwgYWxwaGE9MC41LCBiaW53aWR0aD0xMCkrCiAgc2NhbGVfY29sb3JfbWFudWFsKHZhbHVlcz1jKCJsaWdodGJsdWUiLCAiY29yYWwiKSkrCiAgc2NhbGVfZmlsbF9tYW51YWwodmFsdWVzPWMoImxpZ2h0Ymx1ZSIsICJjb3JhbCIpKSArCiAgdGhlbWVfY2xhc3NpYygpICsKICBnZ3RpdGxlKCJQcmV2aW91cyBEaXN0cmlidXRpb24gQ29sb3JlZCBieSBZIikgKwogIHRoZW1lKHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoaGp1c3QgPSAwLjUsIHNpemU9MTQsIGZhY2U9ImJvbGQiKSkgKwogIHhsYWIoInByZXZpb3VzOiBudW1iZXIgb2YgY29udGFjdHMgcGVyZm9ybWVkIGJlZm9yZSB0aGlzIGNhbXBhaWduIikKYGBgCgpQcmV2aW91cyBpcyBub3QgdW5iYWxhbmNlZC4gUmVtb3ZlIHByZXZpb3VzID4gMTAgYW5kIHBsb3QgYWdhaW4KCmBgYHtyfQpsaWJyYXJ5KGdncGxvdDIpCmRmX3ByZXYgPC0gYmFua1tiYW5rJHByZXZpb3VzIDwgMTAsXQpnZ3Bsb3QoZGZfcHJldiwgYWVzKHg9cHJldmlvdXMsIGZpbGw9eSwgY29sb3I9eSkpKwogIGdlb21faGlzdG9ncmFtKHBvc2l0aW9uPSJpZGVudGl0eSIsIGFscGhhPTAuNSwgYmlud2lkdGg9MSkrCiAgc2NhbGVfY29sb3JfbWFudWFsKHZhbHVlcz1jKCJsaWdodGJsdWUiLCAiY29yYWwiKSkrCiAgc2NhbGVfZmlsbF9tYW51YWwodmFsdWVzPWMoImxpZ2h0Ymx1ZSIsICJjb3JhbCIpKSArCiAgdGhlbWVfY2xhc3NpYygpICsKICBnZ3RpdGxlKCJQcmV2aW91cyAoIDwgMTApIERpc3RyaWJ1dGlvbiBDb2xvcmVkIGJ5IFkiKSArCiAgdGhlbWUocGxvdC50aXRsZSA9IGVsZW1lbnRfdGV4dChoanVzdCA9IDAuNSwgc2l6ZT0xNCwgZmFjZT0iYm9sZCIpKSsKICB4bGFiKCJwcmV2aW91czogbnVtYmVyIG9mIGNvbnRhY3RzIHBlcmZvcm1lZCBiZWZvcmUgdGhpcyBjYW1wYWlnbiIpCmBgYAoKVHdvIGNvbG9ycyBoYXMgc29tZSBzb3J0IG9mIHNlcGVyYXRpb24uCgpgYGB7cn0Kc3BpbmVwbG90KHggPSBiYW5rJHByZXZpb3VzLCB5ID0gYmFuayR5LCB4bGFiID0gInByZXZpb3VzOiBudW1iZXIgb2YgY29udGFjdHMgcGVyZm9ybWVkIGJlZm9yZSB0aGlzIGNhbXBhaWduIiwgeWxhYiA9ICJ5IiwgYnJlYWtzPTIwMCwgbWFpbiA9ICJQcmV2aW91cyB2cyBZIiwgY29sID0gYygibGlnaHRibHVlIiwgICJjb3JhbCIpKQpgYGAKCkxlc3MgdGhhbiA1MCUgb2YgcGVvcGxlIHRoYXQgaGFzIG5vIHByZXZpb3VzIGNvbnRhY3Qgc3Vic2NyaWJlIGEgdGVybSBkZXBvc2l0LiBBbmQgbW9zdCBwZW9wbGUgZG9uJ3QgaGF2ZSBwcmV2aW91cyBjb250YWN0LiBBbmQgbW9yZSBwcmV2aW91cyBjb250YWN0IHNlZW1zIHRvIGxlYWQgdG8gbW9yZSBwZXJjZW50YWdlIG9mIHBlb3BsZSB3aG8gc3Vic2NyaWJlIGEgdGVybSBkZXBvc2l0LiBNYXkgYmUgcHJldmlvdXMgc2hvdWxkIGJlIGNvbnZlcnQgaW50byBib29sZWFuLCAwID0gZmFsc2UsID4wID0gdHJ1ZQoKYGBge3J9CnRlbSA8LSBiYW5rCnRlbSRwcmV2aW91czEgPC0gdGVtJHByZXZpb3VzID4gMApgYGAKCmBgYHtyfQp0ZW0kcHJldmlvdXMxIDwtIGFzLmZhY3Rvcih0ZW0kcHJldmlvdXMxKQpgYGAKCgpgYGB7cn0Kc3BpbmVwbG90KHggPSB0ZW0kcHJldmlvdXMxLCB5ID0gdGVtJHksIHhsYWIgPSAiUHJldmlvdXMgKGJvb2wpIiwgeWxhYiA9ICJ5IiwgYnJlYWtzPWxpbXMsCiAgICAgICAgICBtYWluID0gIlByZXZpb3VzIChib29sKSB2cyBZIiwgY29sID0gYygibGlnaHRibHVlIiwgImNvcmFsIiksIHhheGxhYmVscyA9IGxldmVscyh0ZW0kcHJldmlvdXMxKSkKYGBgCgpOb3cgdGhlIHZhcmlhYmxlIG1ha2VzIG1vcmUgc2Vuc2UuCgoKYGBge3J9CmxpYnJhcnkoZ2dwbG90MikKZ2dwbG90KGJhbmssIGFlcyh4PXBkYXlzLCBmaWxsPXksIGNvbG9yPXkpKSsKICBnZW9tX2hpc3RvZ3JhbShwb3NpdGlvbj0iaWRlbnRpdHkiLCBhbHBoYT0wLjUsIGJpbndpZHRoPTEwMCkrCiAgc2NhbGVfY29sb3JfbWFudWFsKHZhbHVlcz1jKCJsaWdodGJsdWUiLCAiY29yYWwiKSkrCiAgc2NhbGVfZmlsbF9tYW51YWwodmFsdWVzPWMoImxpZ2h0Ymx1ZSIsICJjb3JhbCIpKSArCiAgdGhlbWVfY2xhc3NpYygpICsKICBnZ3RpdGxlKCJQZGF5cyBEaXN0cmlidXRpb24gQ29sb3JlZCBieSBZIikgKwogIHRoZW1lKHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoaGp1c3QgPSAwLjUsIHNpemU9MTQsIGZhY2U9ImJvbGQiKSkrCiAgeGxhYigicGRheXM6IG51bWJlciBvZiBkYXlzIHRoYXQgcGFzc2VkIGJ5IGFmdGVyIHRoZSBjbGllbnQgd2FzIFxubGFzdCBjb250YWN0ZWQgZnJvbSBhIHByZXZpb3VzIGNhbXBhaWduICgtMSBtZWFucyBjbGllbnQgd2FzIG5vdCBwcmV2aW91c2x5IGNvbnRhY3RlZCkiKQpgYGAKCiJQZGF5cyIgYXJlIG1vc3QgLTEgKC0xIG1lYW5zIGNsaWVudCB3YXMgbm90IHByZXZpb3VzbHkgY29udGFjdGVkKS4gU28gdGhpcyB2YXJpYWJsZSBzaG91bGQgaGF2ZSBhIGxvdCBvZiBkdXBsaWNhdGUgYXMgdGhlICJwcmV2aW91cyIgdmFyaWFibGUuCgpgYGB7cn0KbGlicmFyeShnZ3Bsb3QyKQpkZl9wZGF5cyA8LSBiYW5rW2JhbmskcGRheXMgPT0gLTEsXQpnZ3Bsb3QoZGZfcGRheXMsIGFlcyh4PXBkYXlzLCBmaWxsPXksIGNvbG9yPXkpKSsKICBnZW9tX2hpc3RvZ3JhbShwb3NpdGlvbj0iaWRlbnRpdHkiLCBhbHBoYT0wLjMsIGJpbndpZHRoPTAuNSkrCiAgc2NhbGVfY29sb3JfbWFudWFsKHZhbHVlcz1jKCJsaWdodGJsdWUiLCAiY29yYWwiKSkrCiAgc2NhbGVfZmlsbF9tYW51YWwodmFsdWVzPWMoImxpZ2h0Ymx1ZSIsICJjb3JhbCIpKSArCiAgdGhlbWVfY2xhc3NpYygpICsKICBnZ3RpdGxlKCJQZGF5cyAoPS0xKSBEaXN0cmlidXRpb24gQ29sb3JlZCBieSBZIikgKwogIHRoZW1lKHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoaGp1c3QgPSAwLjUsIHNpemU9MTQsIGZhY2U9ImJvbGQiKSkrCiAgeGxhYigicGRheXM6IG51bWJlciBvZiBkYXlzIHRoYXQgcGFzc2VkIGJ5IGFmdGVyIHRoZSBjbGllbnQgd2FzIFxubGFzdCBjb250YWN0ZWQgZnJvbSBhIHByZXZpb3VzIGNhbXBhaWduICgtMSBtZWFucyBjbGllbnQgd2FzIG5vdCBwcmV2aW91c2x5IGNvbnRhY3RlZCkiKQpgYGAKCmBgYHtyfQpzcGluZXBsb3QoeCA9IGJhbmskcGRheXMsIHkgPSBiYW5rJHksIHhsYWIgPSAicGRheXMiLCB5bGFiID0gInkiLCBicmVha3M9MTAwMCwgbWFpbiA9ICJQZGF5cyB2cyBZIiwgY29sID0gYygibGlnaHRibHVlIiwgICJjb3JhbCIpKQpgYGAKCmBgYHtyfQpkZl9wZGF5cyA8LSBiYW5rW2JhbmskcGRheXMgPjAsIF0Kc3BpbmVwbG90KHggPSBkZl9wZGF5cyRwZGF5cywgeSA9IGRmX3BkYXlzJHksIHhsYWIgPSAicGRheXMiLCB5bGFiID0gInkiLCBicmVha3M9MTAwLCBtYWluID0gIlBkYXlzICg+MCkgdnMgWSIsIGNvbCA9IGMoImxpZ2h0Ymx1ZSIsICAiY29yYWwiKSkKYGBgCgpgYGB7cn0KYmFua1tiYW5rJHBkYXlzPjAsXQpgYGAKCgpNb3N0IGNsaWVudHMgZGlkbid0IGdldCBjb250YWN0IHByZXZpb3VzLCBhbmQgbGVzcyB0aGFuIGhhbGYgb2YgdGhlbSBzdWJzY3JpYmUgYSB0ZXJtIGRlcG9zaXQuIEZvciB0aG9zZSB3aG8gZ290IGNvbnRhY3RlZCwgcGRheXMgODAtMTAwIGFuZCAxODAtMTkwIGhhdmUgdGhlIGhpZ2hlc3Qgc3Vic2NyaWJlIHJhdGUgKG1vcmUgdGhhbiA4MCUpLgoKQ29udmVydCAicGRheXMiIGludG8gYm9vbAoKYGBge3J9CnRlbSRwZGF5czEgPC0gdGVtJHBkYXlzID4gMApgYGAKCmBgYHtyfQp0ZW0kcGRheXMxIDwtIGFzLmZhY3Rvcih0ZW0kcGRheXMxKQpgYGAKCmBgYHtyfQpzcGluZXBsb3QoeCA9IHRlbSRwZGF5czEsIHkgPSB0ZW0keSwgeGxhYiA9ICJQZGF5cyAoYm9vbCkiLCB5bGFiID0gInkiLCBicmVha3M9bGltcywKICAgICAgICAgIG1haW4gPSAiUGRheXMgKGJvb2wpIHZzIFkiLCBjb2wgPSBjKCJsaWdodGJsdWUiLCAiY29yYWwiKSwgeGF4bGFiZWxzID0gbGV2ZWxzKHRlbSRwZGF5czEpKQpgYGAKCkNvbXBhcmUgcHJldmlvdXMgYW5kIHBkYXlzCgpgYGB7cn0KdGVtJHByZXZpb3VzMSA8LSBhcy5mYWN0b3IodGVtJHByZXZpb3VzMSkKYGBgCgoKYGBge3J9CnNwaW5lcGxvdCh4ID0gdGVtJHBkYXlzMSwgeSA9IHRlbSRwcmV2aW91czEsIHhsYWIgPSAiUGRheXMgKGJvb2wpIiwgeWxhYiA9ICJ5IiwgYnJlYWtzPWxpbXMsCiAgICAgICAgICBtYWluID0gIlBkYXlzIChib29sKSB2cyBQcmV2aW91cyAoYm9vbCkgIiwgY29sID0gYygibGlnaHRibHVlIiwgImNvcmFsIiksIHhheGxhYmVscyA9IGxldmVscyh0ZW0kcGRheXMxKSkKYGBgCgpwZGF5cyAoYm9vbCkgYW5kIHByZXZpb3VzIChib29sKSBhcmUgdG90YWwgb3ZlcmxhcC4gU2luY2UgcGRheXMgaXMgc3Ryb25nbHkgYXNzb2NpYXRlZCB3aXRoIHByZXZpb3VzIGFuZCBwb3V0Y29tZSAoc2hvd24gbGF0ZXIpLiBJdCBzaG91bGQgYmUgZHJvcGVkIHdoZW4gcGVyZm9ybWluZyBjbGFzc2lmaWNhdGlvbi4KCkNvbWJpbmUgY3BtcGFpZ24gYW5kIHByZXZpb3VzLCBuYW1lZCAiY29udGFjdF90aW1lcyIKCmBgYHtyfQp0ZW0kY29udGFjdF90aW1lcyA8LSB0ZW0kY2FtcGFpZ24gKyB0ZW0kcHJldmlvdXMKYGBgCgpgYGB7cn0KbGlicmFyeShnZ3Bsb3QyKQpnZ3Bsb3QodGVtLCBhZXMoeD1jb250YWN0X3RpbWVzLCBmaWxsPXksIGNvbG9yPXkpKSsKICBnZW9tX2hpc3RvZ3JhbShwb3NpdGlvbj0iaWRlbnRpdHkiLCBhbHBoYT0wLjUsIGJpbndpZHRoPTUpKwogIHNjYWxlX2NvbG9yX21hbnVhbCh2YWx1ZXM9YygibGlnaHRibHVlIiwgImNvcmFsIikpKwogIHNjYWxlX2ZpbGxfbWFudWFsKHZhbHVlcz1jKCJsaWdodGJsdWUiLCAiY29yYWwiKSkgKwogIHRoZW1lX2NsYXNzaWMoKSArCiAgZ2d0aXRsZSgiQ29udGFjdF9UaW1lcyAoQ2FtcGFpZ24gKyBQcmV2aW91cykgRGlzdHJpYnV0aW9uIENvbG9yZWQgYnkgWSIpICsKICB0aGVtZShwbG90LnRpdGxlID0gZWxlbWVudF90ZXh0KGhqdXN0ID0gMC41LCBzaXplPTE0LCBmYWNlPSJib2xkIikpKwogIHhsYWIoImNvbnRhY3RfdGltZXM6IHRoZSB0b3RhbCBudW1iZXIgb2YgYmVpbmcgY29udGFjdGVkIFxuYmVmb3JlIHRoaXMgY2FtcGFpZ24gYW5kIGR1cmluZyB0aGlzIGNvbXBhaWduIikKYGBgCgpgYGB7cn0Kc3BpbmVwbG90KHggPSB0ZW0kY29udGFjdF90aW1lcywgeSA9IHRlbSR5LCB4bGFiID0gImNvbnRhY3RfdGltZXM6IHRoZSB0b3RhbCBudW1iZXIgb2YgYmVpbmcgY29udGFjdGVkIFxuYmVmb3JlIHRoaXMgY2FtcGFpZ24gYW5kIGR1cmluZyB0aGlzIGNvbXBhaWduIiwgeWxhYiA9ICJ5IiwgYnJlYWtzPTIwMCwKICAgICAgICAgIG1haW4gPSAiQ29udGFjdF9UaW1lcyAoQ2FtcGFpZ24gKyBQcmV2aW91cykgdnMgWSIsIGNvbCA9IGMoImxpZ2h0Ymx1ZSIsICAiY29yYWwiKSkKYGBgCgpUaGUgY29udGFjdF90aW1lcyB2YXJpYWJsZSBzaG93cyBsZXNzIHBvd2VyIG9mIHNwbGl0dGluZyAieWVzIiBhbmQgIm5vIiBpbiB0aGUgeSB2YXJpYWJsZS4gU28gaXQgc2hvdWxkIG5vdCBiZSB1c2VkLgoKUGxvdCBtb250aDoKCmBgYHtyfQpnZ3Bsb3QoYmFuaywgYWVzKHg9bW9udGgsIGZpbGw9eSwgY29sb3I9eSkpKwogIGdlb21faGlzdG9ncmFtKHBvc2l0aW9uPSJpZGVudGl0eSIsIGFscGhhPTAuNSwgc3RhdD0iY291bnQiKSsKICBzY2FsZV9jb2xvcl9tYW51YWwodmFsdWVzPWMoImxpZ2h0Ymx1ZSIsICJjb3JhbCIpKSsKICBzY2FsZV9maWxsX21hbnVhbCh2YWx1ZXM9YygibGlnaHRibHVlIiwgImNvcmFsIikpICsKICB0aGVtZV9jbGFzc2ljKCkgKwogIGdndGl0bGUoIk1vbnRoIERpc3RyaWJ1dGlvbiBDb2xvcmVkIGJ5IFkiKSArCiAgdGhlbWUocGxvdC50aXRsZSA9IGVsZW1lbnRfdGV4dChoanVzdCA9IDAuNSwgc2l6ZT0xNCwgZmFjZT0iYm9sZCIpKQpgYGAKClR3byBjb2xvcnMgaGF2ZSBzb21lIHNvcnQgb2Ygc2VwZXJhdGlvbi4KClBsb3QgZGF5IG9mIG1vbnRoOgoKYGBge3J9CmdncGxvdChiYW5rLCBhZXMoeD1kYXksIGZpbGw9eSwgY29sb3I9eSkpKwogIGdlb21faGlzdG9ncmFtKHBvc2l0aW9uPSJpZGVudGl0eSIsIGFscGhhPTAuNSwgc3RhdD0iY291bnQiKSsKICBzY2FsZV9jb2xvcl9tYW51YWwodmFsdWVzPWMoImxpZ2h0Ymx1ZSIsICJjb3JhbCIpKSsKICBzY2FsZV9maWxsX21hbnVhbCh2YWx1ZXM9YygibGlnaHRibHVlIiwgImNvcmFsIikpICsKICB0aGVtZV9jbGFzc2ljKCkgKwogIGdndGl0bGUoIkRheSBEaXN0cmlidXRpb24gQ29sb3JlZCBieSBZIikgKwogIHRoZW1lKHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoaGp1c3QgPSAwLjUsIHNpemU9MTQsIGZhY2U9ImJvbGQiKSkKYGBgCgpUd28gY29sb3JzIGhhdmUgc29tZSBzZXBlcmF0aW9uLgoKX19Db21iaW5lIGRheSBhbmQgbW9udGhfXwoKYGBge3J9CnN1bW1hcnkoYmFua1ssIGMoImRheSIsICJtb250aCIpXSkKYGBgCgpgYGB7cn0KbGlicmFyeShtYWdyaXR0cikKbGlicmFyeShsdWJyaWRhdGUpCm1vMk51bSA8LSBmdW5jdGlvbih4KSBtYXRjaCh0b2xvd2VyKHgpLCB0b2xvd2VyKG1vbnRoLmFiYikpCm1vbnRoIDwtIGFzLmRvdWJsZShtbzJOdW0oYmFuayRtb250aCkpCmRheSA8LSBhcy5kb3VibGUoYmFuayRkYXkpCm1fZCA8LSBwYXN0ZSgiMjAxOCIsIG1vbnRoLCBkYXksIHNlcD0iLSIpICU+JSB5bWQoKSAlPiUgYXMuRGF0ZSgpCm1fZCA8LSBzZXROYW1lcyhkYXRhLmZyYW1lKG1fZCksICJkYXRlIikKaGVhZChtX2QpCmBgYAoKYGBge3J9CnRlbSA8LSBjYmluZCh0ZW0sIG1fZCkKYGBgCgpgYGB7cn0KI3JlbW92ZSB0aGUgeWVhcgp0ZW0kZGF0ZSA8LSBmb3JtYXQodGVtJGRhdGUsIGZvcm1hdD0iJW0tJWQiKQp0ZW0gPC0gdGVtWywgLTE5XQpoZWFkKHRlbSkKYGBgCgpQbG90IGRhdGUgKGRheSBhbmQgbW9udGgpCgpgYGB7cn0KbGVuZ3RoKHVuaXF1ZSh0ZW0kZGF0ZSkpCmBgYAoKR3JvdXAgInkiIGJ5IGVhY2ggdW5pcXVlIGRhdGUKCmBgYHtyfQpsaWJyYXJ5KGRwbHlyKQpkYXRlX3kgPC0gdGVtICU+JSAKICBncm91cF9ieShkYXRlLCB5KSAlPiUgCiAgc3VtbWFyaXNlKG4gPSBuKCkpCmhlYWQoZGF0ZV95KQpgYGAKCmBgYHtyfQpkYXRlX3kgPC0gZGF0YS5mcmFtZShkYXRlX3kpCmBgYAoKYGBge3J9CmRhdGVfeV95ZXMgPC0gZGF0ZV95W2RhdGVfeSR5PT0ieWVzIiwgYygiZGF0ZSIsICJuIildCnJvd25hbWVzKGRhdGVfeV95ZXMpIDwtIGRhdGVfeV95ZXMkZGF0ZQpoZWFkKGRhdGVfeV95ZXMpCmBgYAoKYGBge3J9CmRhdGVfeV9ubyA8LSBkYXRlX3lbZGF0ZV95JHk9PSJubyIsIGMoImRhdGUiLCAibiIpXQpyb3duYW1lcyhkYXRlX3lfbm8pIDwtIGRhdGVfeV9ubyRkYXRlCmhlYWQoZGF0ZV95X25vKQpgYGAKCmBgYHtyfQoKZ2dwbG90KGRhdGE9ZGF0ZV95LCBhZXMoeD1kYXRlLCB5PSBuLCBncm91cCA9IHksIGZpbGw9eSwgY29sb3I9eSkpICsgZ2VvbV9saW5lKCkrCiAgc2NhbGVfY29sb3JfbWFudWFsKHZhbHVlcz1jKCJsaWdodGJsdWUiLCAiY29yYWwiKSkrCiAgc2NhbGVfZmlsbF9tYW51YWwodmFsdWVzPWMoImxpZ2h0Ymx1ZSIsICJjb3JhbCIpKSArCiAgdGhlbWVfY2xhc3NpYygpICsKICBnZ3RpdGxlKCJEYXRlIERpc3RyaWJ1dGlvbiBDb2xvcmVkIGJ5IFkiKSsKICB0aGVtZShwbG90LnRpdGxlID0gZWxlbWVudF90ZXh0KGhqdXN0ID0gMC41LCBzaXplPTE0LCBmYWNlPSJib2xkIiksIGF4aXMudGV4dC54PSBlbGVtZW50X2JsYW5rKCkpICsKICB5bGFiKCJjb3VudCIpCmBgYAoKVGhlIHR3byBjb2xvcnMgc2VwZXJhdGVkIGEgbGl0dGxlLCBidXQgb3ZlcmFsbCBpdCBzZWVtcyB0byBiZSB0aGUgc2FtZSBhcyBtb250aC4gU28gaXQgc2hvdWJlIGJlIGJldHRlciB0byBrZWVwIG1vbnRoIGFuZCBkYXkgc2VwZXJhdGVkLgoKCmBgYHtyfQpnZ3Bsb3QoYmFuaywgYWVzKHg9cG91dGNvbWUsIGZpbGw9eSwgY29sb3I9eSkpKwogIGdlb21faGlzdG9ncmFtKHBvc2l0aW9uPSJpZGVudGl0eSIsIGFscGhhPTAuNSwgc3RhdD0iY291bnQiKSsKICBzY2FsZV9jb2xvcl9tYW51YWwodmFsdWVzPWMoImxpZ2h0Ymx1ZSIsICJjb3JhbCIpKSsKICBzY2FsZV9maWxsX21hbnVhbCh2YWx1ZXM9YygibGlnaHRibHVlIiwgImNvcmFsIikpICsKICB0aGVtZV9jbGFzc2ljKCkgKwogIGdndGl0bGUoIlBvdXRjb21lIERpc3RyaWJ1dGlvbiBDb2xvcmVkIGJ5IFkiKSArCiAgdGhlbWUocGxvdC50aXRsZSA9IGVsZW1lbnRfdGV4dChoanVzdCA9IDAuNSwgc2l6ZT0xNCwgZmFjZT0iYm9sZCIpKQpgYGAKClN1Y2Nlc3MgaGFzIG11Y2ggbW9yZSAieWVzIiB0aGFuICJubyIuCgpgYGB7cn0KbGV2ZWxzKGJhbmskcG91dGNvbWUpCmBgYAoKCkZyb20gdGhlIHBhaXIgcGxvdCwgInBkYXlzIiBhbmQgInBvdXRjb21lIiB0b2dldGhlciBjYW4gaWRlbnRpZnkgbW9zdCBvbmUgY2F0ZWdvcnkgb2YgInkiIGFuZCBjYW4gYmFyZWx5IGlkZW50aWZ5IHRoZSBvdGhlci4gInkiIGlzIGJvb2xlYW4sIHdoaWNoIHJlZCBjb3JyZWxhCgpgYGB7cn0KcGFyKGxhcyA9IDIsIGNleC5heGlzID0gMC43NSwgbWFyID0gYyg3LCA0LjEsIDQuMSwgMi4xKSkKc3BpbmVwbG90KHggPSBiYW5rJHBvdXRjb21lLCB5ID0gYmFuayR5LCB4bGFiID0gInBvdXRjb21lIiwgeWxhYiA9ICJ5IiwgYnJlYWtzPWxpbXMsCiAgICAgICAgICBtYWluID0gInBvdXRjb21lIHZzIHkiLCBjb2wgPSBjKCJsaWdodGJsdWUiLCAgImNvcmFsIikpCmBgYAoKSWYgYSBjbGllbnQgc3Vic3JpYmVkIGEgdGVybSBkZXBvc2l0LCBoZS8gc2hlIGlzIHZlcnkgbGlrZWx5IHRvIHN1YnNjcmliZSBpbiB0aGlzIGNhbXBhaWduLiBQb3V0Y29tZSBzZWVtcyB0byBiZSBhIHN0cm9uZyBwcmVkaWN0b3IuCgpQbG90IHBkYXlzIGFuZCBwb3V0Y29tZQoKCmBgYHtyfQpwYXIobGFzID0gMSwgY2V4LmF4aXMgPSAwLjc1LCBtYXIgPSBjKDcsIDQuMSwgNC4xLCAyLjEpKQpzcGluZXBsb3QoeCA9IGJhbmskcGRheSwgeSA9IGJhbmskcG91dGNvbWUsIHhsYWIgPSAicGRheXMiLCB5bGFiID0gInBvdXRjb21lIiwgYnJlYWtzPTEwMDAsCiAgICAgICAgICBtYWluID0gInBkYXlzIHZzIHBvdXRjb21lIiwgY29sID0gYygibGlnaHRibHVlIiwgImdyZXkiLCJjb3JhbCIsICJ3aGl0ZSIpKQpgYGAKCgpgYGB7cn0KZGZwcCA8LSBiYW5rW2JhbmskcGRheXMgPi0xLCBdCnBhcihsYXMgPSAxLCBjZXguYXhpcyA9IDAuNzUsIG1hciA9IGMoNywgNC4xLCA0LjEsIDIuMSkpCnNwaW5lcGxvdCh4ID0gZGZwcCRwZGF5LCB5ID0gZGZwcCRwb3V0Y29tZSwgeGxhYiA9ICJwZGF5cyIsIHlsYWIgPSAicG91dGNvbWUiLCBicmVha3M9MjAwMCwKICAgICAgICAgIG1haW4gPSAicGRheXMgdnMgcG91dGNvbWUiLCBjb2wgPSBjKCJsaWdodGJsdWUiLCAiZ3JleSIsImNvcmFsIiwgIndoaXRlIikpCmBgYAoKCkFsbCBwb3V0Y29tZSBpcyB1bmtub3duIGlmIHBkYXlzID0gLTEsIG1lYW5pbmcgdGhlIGNsaWVudCB3YXMgbm90IGNvbnRhY3RlZCBmcm9tIGEgcHJldmlvdXMgY29tcGFpZ24uCgpNb3N0IG9mIHRoZSBjbGllbnRzIHdpdGggcGRheXMgb2YgODgtOTMgYW5kIDE3NiAtIDE4NiB0aGF0IHdlcmUgcHJldmlvdXNseSBjb250YWN0ZWQgc3Vic2NyaWJlZCBhIHRlcm0gZGVwb3NpdC4KClBkYXlzIHNlZW1zIHRvIGJlIHN0cm9uZ2x5IGNvcnJlbGF0ZWQgdG8gcG91dGNvbWUuIFNvIGl0IG1heSBiZSByZW1vdmVkLgoKCmBgYHtyfQpwYXIobGFzID0gMiwgY2V4LmF4aXMgPSAwLjc1LCBtYXIgPSBjKDcsIDQuMSwgNC4xLCAyLjEpKQpzcGluZXBsb3QoeCA9IGJhbmskam9iLCB5ID0gYmFuayR5LCB4bGFiID0gImpvYiIsIHlsYWIgPSAieSIsCiAgICAgICAgICBtYWluID0gIkpvYiB2cyBZIiwgY29sID0gYygibGlnaHRibHVlIiwgImNvcmFsIiksIHhheGxhYmVscyA9IGxldmVscyhiYW5rJGpvYikpCgoKYGBgCgpPdmVyYWxsLCBqb2IgaGFzIHNvbWUgZGlmZmVyZW5jZSBpbiAieWVzIiBhbmQgIm5vIiBhbW9uZyBpdHMgY2F0ZWdvcmllcy4gSXQgbWF5IGJlIGEgZ29vZCBwcmVkaWN0b3IuCgoKYGBge3J9CnNwaW5lcGxvdCh4ID0gYmFuayRtYXJpdGFsLCB5ID0gYmFuayR5LCB4bGFiID0gIm1hcml0YWwiLCB5bGFiID0gInkiLAogICAgICAgICAgbWFpbiA9ICJNYXJpdGFsIHZzIFkiLCBjb2wgPSBjKCJsaWdodGJsdWUiLCAiY29yYWwiKSwgeGF4bGFiZWxzID0gbGV2ZWxzKGJhbmskbWFyaXRhbCkpCmBgYAoKQm90aCBtYXJpdGFsIGhhdmUgZXF1YWwgbnVtYmVyIG9mICJ5ZXMiIGFuZCAibm8iIGFtb25nIGl0cyBjYXRlZ29yaWVzLiBJdCBzZWVtcyB0byBiZSBhIHZlcnkgd2VhayBwcmVkaWN0b3IuCgoKYGBge3J9CnNwaW5lcGxvdCh4ID0gYmFuayRqb2IsIHkgPSBiYW5rJG1hcml0YWwsIHhsYWIgPSAibWFyaXRhbCIsIHlsYWIgPSAiam9iIiwKICAgICAgICAgIG1haW4gPSAiTWFyaXRhbCB2cyBKb2IiLCBjb2wgPSBjKCJsaWdodGJsdWUiLCAiY29yYWwiLCAiZ3JleSIpLCB4YXhsYWJlbHMgPSBsZXZlbHMoYmFuayRqb2IpKQpgYGAKCk1vc3Qgc3R1ZGVudHMgYXJlIHNpbmdsZSBhbmQgbW9zdCByZXRpcmVkIHBlb3BlbGUgYXJlIG1hcnJpZWQuIEJ1dCB0aGUgdHdvIHZhcmlhYmxlcyBhcmUgcHJldHR5IG1peGVkIG92ZXJhbGwuIFNvIHRoZXkgc2VlbSBub3Qgc3Ryb25nbHkgYXNzb2NpYXRlZC4KCgpgYGB7cn0Kc3BpbmVwbG90KHggPSBiYW5rJGVkdWNhdGlvbiwgeSA9IGJhbmskeSwgeGxhYiA9ICJlZHVjYXRpb24iLCB5bGFiID0gInkiLAogICAgICAgICAgbWFpbiA9ICJFZHVjYXRpb24gdnMgWSIsIGNvbCA9IGMoImxpZ2h0Ymx1ZSIsICJjb3JhbCIpLCB4YXhsYWJlbHMgPSBsZXZlbHMoYmFuayRlZHVjYXRpb24pKQpgYGAKClRoZSAieWVzIiBhbmQgIm5vIiBhcmUgcHJldHR5IGV2ZW5seSBzZXBlcmF0ZWQuICJlZHVjYXRpb24iIG1heSBub3QgYmUgYSBnb29kIHByZWRpY3Rvci4KCgpgYGB7cn0Kc3BpbmVwbG90KHggPSBiYW5rJGRlZmF1bHQsIHkgPSBiYW5rJHksIHhsYWIgPSAiZGVmYXVsdCIsIHlsYWIgPSAieSIsCiAgICAgICAgICBtYWluID0gIkRlZmF1bHQgdnMgWSIsIGNvbCA9IGMoImxpZ2h0Ymx1ZSIsICJjb3JhbCIpLCB4YXhsYWJlbHMgPSBsZXZlbHMoYmFuayRkZWZhdWx0KSkKYGBgCgoiZGVmYXVsdCIgYWxtb3N0IHBlcmZlY3RseSBzcGxpdCB5ZXMgYW5kIG5vLiBPYnZpb3VzbHksIGl0IGhhcyBhbG1vc3Qgbm90IGFzc29jaWF0aW9uIHdpdGggInkiLgoKCmBgYHtyfQpwYXIobGFzID0gMiwgY2V4LmF4aXMgPSAwLjc1LCBtYXIgPSBjKDcsIDQuMSwgNC4xLCAyLjEpKQpzcGluZXBsb3QoeCA9IGJhbmskam9iLCB5ID0gYmFuayRkZWZhdWx0LCB4bGFiID0gImpvYiIsIHlsYWIgPSAiZGVmYXVsdCIsCiAgICAgICAgICBtYWluID0gIkRlZmF1bHQgdnMgSm9iIiwgY29sID0gYygibGlnaHRibHVlIiwgImNvcmFsIiksIHhheGxhYmVscyA9IGxldmVscyhiYW5rJGpvYikpCmBgYAoKQWxtb3N0IGFsbCByZXRpcmVkIGFuZCBzdHVkZW50IGNsaWVudHMgaGF2ZSBubyBkZWZ1bHQuIEFuZCBhbGwgb3RoZXIgam9icyBoYXZlIGRlZmF1bHQuIERlZmF1bHQgaGFzIHZlcnkgc21hbGwgbnVtYmVyICJ5ZXMiIGFuZCBpdCBkb2Vzbid0IGlkZW50aWZ5ICJ5IiB3ZWxsLiAiZGVmYXVsdCIgY2FuIGJlIGRlbGV0ZWQuCgoKYGBge3J9CnNwaW5lcGxvdCh4ID0gYmFuayRob3VzaW5nLCB5ID0gYmFuayR5LCB4bGFiID0gImhvdXNpbmciLCB5bGFiID0gInkiLAogICAgICAgICAgbWFpbiA9ICJIb3VzaW5nIHZzIFkiLCBjb2wgPSBjKCJsaWdodGJsdWUiLCAiY29yYWwiKSwgeGF4bGFiZWxzID0gbGV2ZWxzKGJhbmskaG91c2luZykpCmBgYAoKImhvdXNpbmciIGhhcyBzb21lIGRpZmZlcmVuY2UgaW4gdGhlIHByb3BvcnRpb24gb2YgInllcyIgYW5kICJubyIuIEJ1dCBub3QgdmVyeSBvYnZpb3VzLgoKCmBgYHtyfQpwYXIobGFzID0gMiwgY2V4LmF4aXMgPSAwLjc1LCBtYXIgPSBjKDcsIDQuMSwgNC4xLCAyLjEpKQpzcGluZXBsb3QoeCA9IGJhbmskam9iLCB5ID0gYmFuayRob3VzaW5nLCB4bGFiID0gImpvYiIsIHlsYWIgPSAiaG91c2luZyIsCiAgICAgICAgICBtYWluID0gIkpvYiB2cyBIb3VzaW5nIiwgY29sID0gYygibGlnaHRibHVlIiwgImNvcmFsIiksIHhheGxhYmVscyA9IGxldmVscyhiYW5rJGpvYikpCmBgYAoKImhvdXNpbmciIGlzIHdpZGVseSBzcHJlYWQgdG8gYWxsIGpvYnMuIHNvICJob3VzaW5nIiBzZWVtcyB0byBoYXZlIHdlYWsgYXNzb2NpYXRpb24gd2l0aCAiam9iIi4KCgpgYGB7cn0KZ2dwbG90KGJhbmssIGFlcyh4PWFnZSwgZmlsbD1ob3VzaW5nLCBjb2xvcj1ob3VzaW5nKSkrCiAgZ2VvbV9oaXN0b2dyYW0ocG9zaXRpb249ImlkZW50aXR5IiwgYWxwaGE9MC41KSsKICBzdGF0X2Z1bmN0aW9uKGZ1biA9IGZ1bmN0aW9uKGJhbmssIG1lYW4sIHNkLCBuKXsKICAgIG4gKiBkbm9ybSh4ID0gYmFuaywgbWVhbiA9IG1lYW4sIHNkID0gc2QpIH0sIAogICAgYXJncyA9IHdpdGgoYmFuaywgYyhtZWFuID0gbWVhbihhZ2UpLCBzZCA9IHNkKGFnZSksIG4gCiAgICAgICAgICAgICAgICAgICAgICAgID0gbGVuZ3RoKGFnZSkpKSwgY29sPSJibHVlIikgKwogIHNjYWxlX2NvbG9yX21hbnVhbCh2YWx1ZXM9YygibGlnaHRibHVlIiwgImNvcmFsIikpKwogIHNjYWxlX2ZpbGxfbWFudWFsKHZhbHVlcz1jKCJsaWdodGJsdWUiLCAiY29yYWwiKSkgKwogIHRoZW1lX2NsYXNzaWMoKSArCiAgZ2d0aXRsZSgiQmFsYW5jZSBEaXN0cmlidXRpb24gQ29sb3JlZCBieSBIb3VzaW5nIikgKwogIHRoZW1lKHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoaGp1c3QgPSAwLjUsIHNpemU9MTQsIGZhY2U9ImJvbGQiKSkKYGBgCgoiaG91c2luZyIgc2VlbXMgdG8gYXNzb2NpYXRlZCB3aXRoICJhZ2UiLiBDb3JyZWxhdGlvbiBvZiB0aGVzZSB0d28gc2hvdWxkIGJlIHRlc3RlZC4KCmBgYHtyfQpzcGluZXBsb3QoeCA9IGJhbmskYmFsYW5jZSwgeSA9IGJhbmskaG91c2luZywgeGxhYiA9ICJiYWxhbmNlIiwgeWxhYiA9ICJob3VzaW5nIiwgYnJlYWtzPTEwMCwKICAgICAgICAgIG1haW4gPSAiQmFsYW5jZSB2cyBIb3VzaW5nIiwgY29sID0gYygibGlnaHRibHVlIiwgICJjb3JhbCIpKQpgYGAKCgoiaG91c2luZyIgc2VlbXMgdG8gd2lkZWx5IGluIGJhbGFuY2UuCgpgYGB7cn0Kc3BpbmVwbG90KHggPSBiYW5rJGxvYW4sIHkgPSBiYW5rJHksIHhsYWIgPSAibG9hbiIsIHlsYWIgPSAieSIsCiAgICAgICAgICBtYWluID0gIkxvYW4gdnMgWSIsIGNvbCA9IGMoImxpZ2h0Ymx1ZSIsICJjb3JhbCIpLCB4YXhsYWJlbHMgPSBsZXZlbHMoYmFuayRsb2FuKSkKYGBgCgpObyBkcmFtYWN0aWMgc3BsaXQuCgpgYGB7cn0Kc3BpbmVwbG90KHggPSBiYW5rJGhvdXNpbmcsIHkgPSBiYW5rJGxvYW4sIHhsYWIgPSAiaG91c2luZyIsIHlsYWIgPSAibG9hbiIsCiAgICAgICAgICBtYWluID0gIkhvdXNpbmcgdnMgTG9hbiIsIGNvbCA9IGMoImxpZ2h0Ymx1ZSIsICJjb3JhbCIpLCB4YXhsYWJlbHMgPSBsZXZlbHMoYmFuayRob3VzaW5nKSkKYGBgCgpObyBzdHJvbmcgYXNzb2NpYXRpb24uCgoKYGBge3J9CnBhcihsYXMgPSAyLCBjZXguYXhpcyA9IDAuNzUsIG1hciA9IGMoNywgNC4xLCA0LjEsIDIuMSkpCnNwaW5lcGxvdCh4ID0gYmFuayRjb250YWN0LCB5ID0gYmFuayR5LCB4bGFiID0gImNvbnRhY3QiLCB5bGFiID0gInkiLAogICAgICAgICAgbWFpbiA9ICJDb250YWN0IHZzIFkiLCBjb2wgPSBjKCJsaWdodGJsdWUiLCAiY29yYWwiKSwgeGF4bGFiZWxzID0gbGV2ZWxzKGJhbmskY29udGFjdCkpCmBgYAoKTWF5IGhhdmUgc29tZSBhc3NvY2lhdGlvbi4gCgoKIyMjU3VtbXJ5IG9mIHRoZSBwbG90cwoKSW4gdGhlIGFib3ZlIHZpc3VhbGl6YXRpb24sIHNvbWUgdmFyaWFibGUgc2VlbSB0byBoYXZlIG1vcmUgYXNzb2NpYXRpb24gdGhhbiB0aGUgb3RoZXJzLiBIZXJlIGlzIHRoZSBzdW1tZXJ5IG9mIHRoZSBwcmVkaWN0b3IgdmFyaWFibGVzOiBcbgoKKipHb29kKio6IGR1cmF0aW9uLCBqb2IsIGFnZSwgcG91Y29tZQoqKm1lZGl1bSoqOiBtb250aCwgZGF5LCBwcmV2aW91cyAoYm9vbCkKKipzbWFsbCoqOiBtYXJpdGFsLCBlZHVjYXRpb24sIGJhbGFuY2UsIGhvdXNpbmcsIGxvYW4sIGNvbnRhY3QsIGNhbXBhaWduCioqc2hvdWxkIG5vdCB1c2UqKjogZGVmYXVsdCwgcGRheXMKCgojIFRlc3RzIG9mIEluZGVwZW5kZW5jZQoKUnVuIHRoZSB0ZXN0cyB0byBmdXJ0aGVyIGlkZW50aWZ5IHRoZSBzdHJlbmd0aCBvZiB0aGUgcHJlZGljdG9yIHZhcmlhYmxlcy4KCiMjIyBDaGktU3F1YXJlZCBUZXN0CgpDaGktU3F1YXJlZCBUZXN0IGlzIGZvciB0d28gY2F0ZWdvcmljYWwgdmFyaWFibGVzLiBWYXJpYWJsZXMgaW5jbHVkZTogam9iLCBwb3V0Y29tZSwgbW9udGgsIHByZXZpb3VzKGJvb2wpLCBlZHVjYXRpb24sIGhvdXNpbmcsIGxvYW4sIGNvbnRhY3QsIGRlZmF1bHQKCkJldHdlZW4gcHJlZGljdG9yIHZhcmlhYmxlczogam9iIGFuZCBkZWZhdWx0CgoxLiBHZW5lcmF0ZSBmcmVxdWVjeSB0YWJsZSBcbgoyLiBDaGktc3F1YXJlZCBUZXN0CjMuIENyYW1lcidzIFYgKHRvIGdldCB0aGUgYXNzb2NpYXRpb24gc3RyZW5ndGgpIFxuCjQuIENvbXBhcmUgdGhlIHJlc3VsdCBhbmQgc2VsZWN0IHRoZSBmZWF0dXJlcyB0b2dldGhlciB3aXRoIHRoZSB2aXN1YWxpemF0aW9uIHN1bW1hcnkKClNhbXBsZSAxJSBvZiBkYXRhIGZvciBjaGktc3F1YXJlZCB0ZXN0IG9mIGluZGVwZW5kZW5jZS4KCmBgYHtyfQpzZXQuc2VlZCg3ODkpCmluZGVfdGVzdCA8LSBiYW5rW3NhbXBsZShzZXFfbGVuKG5yb3coYmFuaykpLCBzaXplID0gZmxvb3IoMC4wMSAqIG5yb3coYmFuaykpKSwgXQpucm93KGluZGVfdGVzdCkKYGBgCgpDb250aW5nZW5jeSB0YWJsZXMgb2YgeSB+IGpvYiwgcG91dGNvbWUsIG1vbnRoLCBkYXkgKG5lZWQgdG8gY29udmVydCB0byBmYWN0b3IpLCBwcmV2aW91cyAoYm9vbCksIGVkdWNhdGlvbiwgaG91c2luZywgbG9hbiwgY29udGFjdCBhbmQgZGVmYXVsdAoKX19qb2JfXwoKQ2hpLXNxdWFyZWQgdGVzdDogXG4KSDA6ICBUaGUgdHdvIHZhcmlhYmxlcyBhcmUgaW5kZXBlbmRlbnQuIFxuCkgxOiAgVGhlIHR3byB2YXJpYWJsZXMgYXJlIHJlbGF0ZWQuCgoKYGBge3J9Cnlfam9iIDwtIHRhYmxlKGluZGVfdGVzdCRqb2IsIGluZGVfdGVzdCR5KQp5X2pvYgpgYGAKClBvb2xpbmc6IFdoZW4gYSB2YXJpYWJsZSBoYXMgbW9yZSB0aGFuIHR3byBjYXRlZ29yaWVzLCBhbmQgc29tZSBvZiB0aGVtIGhhdmUgc21hbGwgbnVtYmVycywgaXQgb2Z0ZW4gbWFrZXMgc2Vuc2UgdG8gcG9vbCBzb21lIG9mIHRoZSBjYXRlZ29yaWVzIHRvZ2V0aGVyLgoKQ29tYmluZSAidW5rbm93biIsICJob3VzZW1haWQiIGFuZCBzdHVkZW50IGludG8gYSBuZXcgY2F0ZWdvcnkgIm90aGVycyIKCmBgYHtyfQpqb2JzIDwtIGxldmVscyhpbmRlX3Rlc3Qkam9iKQpqb2JzCmBgYAoKYGBge3J9CmxpYnJhcnkocm9ja2NoYWxrKQppbmRlX3Rlc3QkbmV3X2pvYnMgPC0gY29tYmluZUxldmVscyhpbmRlX3Rlc3Qkam9iLCBsZXZzID0gYygic3R1ZGVudCIsICJob3VzZW1haWQiLCAidW5rbm93biIsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAidW5lbXBsb3llZCIsICJlbnRyZXByZW5ldXIiKSwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG5ld0xhYmVsID0gYygiT3RoZXJzIikpCmBgYAoKCmBgYHtyfQp5X25ld19qb2IgPC0gdGFibGUoaW5kZV90ZXN0JG5ld19qb2IsIGluZGVfdGVzdCR5KQp5X25ld19qb2IKYGBgCgpgYGB7cn0KY2hpc3EudGVzdCh5X25ld19qb2IpCmBgYAoKQ3JpdGljYWwgVmFsdWUgKHVzZSA5NSUgY29uZmlkZW5jZSBsZXZlbCkuIChJZiBDaGkgU3F1YXJlIHZhbHVlID49IENyaXRpY2FsIFZhbHVlLCByZWplY3QgdGhlIG51bGwgaHlwb3RoZXNpcy4KSWYgQ2hpIFNxdWFyZSB2YWx1ZSA8IENyaXRpY2FsIFZhbHVlLCBmYWlsIHRvIHJlamVjdCB0aGUgbnVsbCBoeXBvdGhlc2lzLikKCmBgYHtyfQpkZiA9ICg4LTEpICogKDItMSkKcWNoaXNxKDAuOTUsIGRmKQpgYGAKCmBgYHtyfQpsaWJyYXJ5KHF1ZXN0aW9ucikKam9iX3YgPC0gY3JhbWVyLnYoeV9uZXdfam9iKQpqb2JfdgpgYGAKCgpwLXZhbHVlIGlzIHZlcnkgc21hbGwgYW5kIGNoaS1zcXVhcmVkIHZhbHVlIGlzIGxhcmdlciB0aGFuIGNyaXRpY2FsIHZhbHVlLiBTbyByZWplY3QgbnVsbCBoeXBvdGhlc2lzLiBUaGVyZSBpcyBhc3NvY2lhdGlvbiBiZXR3ZWVuIGpvYiBhbmQgeS4gVGhlIGFzc29jaWF0aW9uIGlzIG1lZGl1bSBzdHJvbmcgYmFzZWQgb24gdGhlIGNyYW1lcidzIHYgdmFsdWUuIAoKX19wb3V0Y29tZV9fCgpgYGB7cn0KbGlicmFyeShxdWVzdGlvbnIpCnByaW50KCJDb250aW5nZW5jeSBUYWJsZSBvZiBQb3V0Y29tZSBhbmQgWToiKQpwb195IDwtIHRhYmxlKGluZGVfdGVzdCRwb3V0Y29tZSwgaW5kZV90ZXN0JHkpCnBvX3kgCgpwcmludCgiQ2hpLVNxdWFyZWQgVGVzdCBvZiBQb3V0Y29tZSBhbmQgWSBUYWJsZToiKQpjaGlzcS50ZXN0KHBvX3kpIApkZiA8LSAobGVuZ3RoKGxldmVscyhpbmRlX3Rlc3QkcG91dGNvbWUpKSAtMSApICogKGxlbmd0aChsZXZlbHMoaW5kZV90ZXN0JHkpKSAtMSApCnByaW50KCJDcml0aWNhbCBWYWx1ZSBvZiBQb3V0Y29tZSBhbmQgWSBUYWJsZToiKQpxY2hpc3EoMC45NSwgZGYpCnByaW50KCJDcmFtZXIuViBvZiBQb3V0Y29tZSBhbmQgWToiKQpwb3V0Y29tZV92IDwtIGNyYW1lci52KHBvX3kpCnBvdXRjb21lX3YKYGBgCgoicG91dGNvbWUiIGlzIGFzc29jaWF0ZWQgd2l0aCAieSIuIFRoZSBhc3NvY2lhdGlvbiBpcyBwcmV0dHkgc3Ryb25nIGFzIGNyYW1lcidzIHYgbGFyZ2VyIHRoYW4gMC4zLgoKX19tb250aF9fCgpgYGB7cn0KbGlicmFyeShxdWVzdGlvbnIpCnByaW50KCJDb250aW5nZW5jeSBUYWJsZSBvZiBNb250aCBhbmQgWToiKQptb250aF95IDwtIHRhYmxlKGluZGVfdGVzdCRtb250aCwgaW5kZV90ZXN0JHkpCm1vbnRoX3kgCgpwcmludCgiQ2hpLVNxdWFyZWQgVGVzdCBvZiBNb250aCBhbmQgWSBUYWJsZToiKQpjaGlzcS50ZXN0KG1vbnRoX3kpIApkZiA8LSAobGVuZ3RoKGxldmVscyhpbmRlX3Rlc3QkbW9udGgpKSAtMSApICogKGxlbmd0aChsZXZlbHMoaW5kZV90ZXN0JHkpKSAtMSApCnByaW50KCJDcml0aWNhbCBWYWx1ZSBvZiBNb250aCBhbmQgWSBUYWJsZToiKQpxY2hpc3EoMC45NSwgZGYpCnByaW50KCJDcmFtZXIuViBvZiBNb250aCBhbmQgWToiKQptb250aF92IDwtIGNyYW1lci52KG1vbnRoX3kpCm1vbnRoX3YKYGBgCgoibW9udGgiIGlzIGFzc29jaWF0ZWQgd2l0aCAieSIsIGFuZCB0aGUgYXNzb2NpYXRpb24gaXMgcHJldHR5IHN0cm9uZy4KCl9fcHJldmlvdXMgKGJvb2wpX18KCmNoYW5nZSBwcmV2aXVvcyBpbnRvIGJvb2wKCmBgYHtyfQppbmRlX3Rlc3QkcHJldmlvdXNfYm9vbCA8LSBpbmRlX3Rlc3QkcHJldmlvdXMgPiAwCmluZGVfdGVzdCRwcmV2aW91c19ib29sIDwtIGFzLmZhY3RvcihpbmRlX3Rlc3QkcHJldmlvdXNfYm9vbCkKYGBgCgoKYGBge3J9CmxpYnJhcnkocXVlc3Rpb25yKQpwcmludCgiQ29udGluZ2VuY3kgVGFibGUgb2YgUHJldmlvdXNfYm9vbCBhbmQgWToiKQpwcmV2aW91c19ib29sX3kgPC0gdGFibGUoaW5kZV90ZXN0JHByZXZpb3VzX2Jvb2wsIGluZGVfdGVzdCR5KQpwcmV2aW91c19ib29sX3kgCgpwcmludCgiQ2hpLVNxdWFyZWQgVGVzdCBvZiBQcmV2aW91c19ib29sIGFuZCBZIFRhYmxlOiIpCmNoaXNxLnRlc3QocHJldmlvdXNfYm9vbF95KSAKZGYgPC0gKGxlbmd0aChsZXZlbHMoaW5kZV90ZXN0JHByZXZpb3VzX2Jvb2wpKSAtMSApICogKGxlbmd0aChsZXZlbHMoaW5kZV90ZXN0JHkpKSAtMSApCnByaW50KCJDcml0aWNhbCBWYWx1ZSBvZiBQcmV2aW91c19ib29sIGFuZCBZIFRhYmxlOiIpCnFjaGlzcSgwLjk1LCBkZikKcHJpbnQoIkNyYW1lci5WIG9mIFByZXZpb3VzX2Jvb2wgYW5kIFk6IikKcHJldmlvdXNfYm9vbF92IDwtIGNyYW1lci52KHByZXZpb3VzX2Jvb2xfeSkKcHJldmlvdXNfYm9vbF92CmBgYAoKIlByZXZpb3VzX2Jvb2wiIGFuZCAieSIgaXMgYXNzb2NpYXRlZCBhbmQgdGhlIGFzc29jaWF0aW9uIHN0cmVuZ3RoIGlzIG1lZGl1bS4KCl9fZWR1Y2F0aW9uX18KCmBgYHtyfQpsaWJyYXJ5KHF1ZXN0aW9ucikKcHJpbnQoIkNvbnRpbmdlbmN5IFRhYmxlIG9mIEVkdWNhdGlvbiBhbmQgWToiKQplZHVjYXRpb25feSA8LSB0YWJsZShpbmRlX3Rlc3QkZWR1Y2F0aW9uLCBpbmRlX3Rlc3QkeSkKZWR1Y2F0aW9uX3kgCgpwcmludCgiQ2hpLVNxdWFyZWQgVGVzdCBvZiBFZHVjYXRpb24gYW5kIFkgVGFibGU6IikKY2hpc3EudGVzdChlZHVjYXRpb25feSkgCmRmIDwtIChsZW5ndGgobGV2ZWxzKGluZGVfdGVzdCRlZHVjYXRpb24pKSAtMSApICogKGxlbmd0aChsZXZlbHMoaW5kZV90ZXN0JHkpKSAtMSApCnByaW50KCJDcml0aWNhbCBWYWx1ZSBvZiBFZHVjYXRpb24gYW5kIFkgVGFibGU6IikKcWNoaXNxKDAuOTUsIGRmKQpwcmludCgiQ3JhbWVyLlYgb2YgRWR1Y2F0aW9uIGFuZCBZOiIpCmVkdWNhdGlvbl92IDwtIGNyYW1lci52KGVkdWNhdGlvbl95KQplZHVjYXRpb25fdgpgYGAKCiJlZHVjYXRpb24iIGFuZCAieSIgaXMgYXNzb2NpYXRlZCBidXQgdGhlIGFzc29jaWF0aW9uIHN0cmVuZ3RoIGlzIHdlYWsuCgpfX2hvdXNpbmdfXwoKYGBge3J9CmxpYnJhcnkocXVlc3Rpb25yKQpwcmludCgiQ29udGluZ2VuY3kgVGFibGUgb2YgSG91c2luZyBhbmQgWToiKQpob3VzaW5nX3kgPC0gdGFibGUoaW5kZV90ZXN0JGhvdXNpbmcsIGluZGVfdGVzdCR5KQpob3VzaW5nX3kgCgpwcmludCgiQ2hpLVNxdWFyZWQgVGVzdCBvZiBIb3VzaW5nIGFuZCBZIFRhYmxlOiIpCmNoaXNxLnRlc3QoaG91c2luZ195KSAKZGYgPC0gKGxlbmd0aChsZXZlbHMoaW5kZV90ZXN0JGhvdXNpbmcpKSAtMSApICogKGxlbmd0aChsZXZlbHMoaW5kZV90ZXN0JHkpKSAtMSApCnByaW50KCJDcml0aWNhbCBWYWx1ZSBvZiBIb3VzaW5nIGFuZCBZIFRhYmxlOiIpCnFjaGlzcSgwLjk1LCBkZikKcHJpbnQoIkNyYW1lci5WIG9mIEhvdXNpbmcgYW5kIFk6IikKaG91c2luZ192IDwtIGNyYW1lci52KGhvdXNpbmdfeSkKaG91c2luZ192CmBgYAoKImhvdXNpbmciIGFuZCAieSIgaXMgYXNzb2NpYXRlZCBhbmQgdGhlIGFzc29jaWF0aW9uIHN0cmVuZ3RoIGlzIG1lZGl1bS4KCgpgYGB7cn0KbGlicmFyeShxdWVzdGlvbnIpCnByaW50KCJDb250aW5nZW5jeSBUYWJsZSBvZiBMb2FuIGFuZCBZOiIpCmxvYW5feSA8LSB0YWJsZShpbmRlX3Rlc3QkbG9hbiwgaW5kZV90ZXN0JHkpCmxvYW5feSAKCnByaW50KCJDaGktU3F1YXJlZCBUZXN0IG9mIExvYW4gYW5kIFkgVGFibGU6IikKY2hpc3EudGVzdChsb2FuX3kpIApkZiA8LSAobGVuZ3RoKGxldmVscyhpbmRlX3Rlc3QkbG9hbikpIC0xICkgKiAobGVuZ3RoKGxldmVscyhpbmRlX3Rlc3QkeSkpIC0xICkKcHJpbnQoIkNyaXRpY2FsIFZhbHVlIG9mIExvYW4gYW5kIFkgVGFibGU6IikKcWNoaXNxKDAuOTUsIGRmKQpwcmludCgiQ3JhbWVyLlYgb2YgTG9hbiBhbmQgWToiKQpsb2FuX3YgPC0gY3JhbWVyLnYobG9hbl95KQpsb2FuX3YKYGBgCgoibG9hbiIgYW5kICJ5IiBpcyB3ZWFrbHkgYXNzb2NpYXRlZC4KCl9fY29udGFjdF9fCgpgYGB7cn0KbGlicmFyeShxdWVzdGlvbnIpCnByaW50KCJDb250aW5nZW5jeSBUYWJsZSBvZiBDb250YWN0IGFuZCBZOiIpCmNvbnRhY3RfeSA8LSB0YWJsZShpbmRlX3Rlc3QkY29udGFjdCwgaW5kZV90ZXN0JHkpCmNvbnRhY3RfeSAKCnByaW50KCJDaGktU3F1YXJlZCBUZXN0IG9mIENvbnRhY3QgYW5kIFkgVGFibGU6IikKY2hpc3EudGVzdChjb250YWN0X3kpIApkZiA8LSAobGVuZ3RoKGxldmVscyhpbmRlX3Rlc3QkY29udGFjdCkpIC0xICkgKiAobGVuZ3RoKGxldmVscyhpbmRlX3Rlc3QkeSkpIC0xICkKcHJpbnQoIkNyaXRpY2FsIFZhbHVlIG9mIENvbnRhY3QgYW5kIFkgVGFibGU6IikKcWNoaXNxKDAuOTUsIGRmKQpwcmludCgiQ3JhbWVyLlYgb2YgQ29udGFjdCBhbmQgWToiKQpjb250YWN0X3YgPC0gY3JhbWVyLnYoY29udGFjdF95KQpjb250YWN0X3YKYGBgCgoiY29udGFjdCIgYW5kICJ5IiBpcyBzdHJvbmdseSBhc3NvY2lhdGVkLgoKX19kZWZhdWx0X18KCmBgYHtyfQpsaWJyYXJ5KHF1ZXN0aW9ucikKcHJpbnQoIkNvbnRpbmdlbmN5IFRhYmxlIG9mIERlZmF1bHQgYW5kIFk6IikKZGVmYXVsdF95IDwtIHRhYmxlKGluZGVfdGVzdCRkZWZhdWx0LCBpbmRlX3Rlc3QkeSkKZGVmYXVsdF95IAoKcHJpbnQoIkNoaS1TcXVhcmVkIFRlc3Qgb2YgQ29udGFjdCBhbmQgWSBUYWJsZToiKQpjaGlzcS50ZXN0KGRlZmF1bHRfeSkgCmRmIDwtIChsZW5ndGgobGV2ZWxzKGluZGVfdGVzdCRkZWZhdWx0KSkgLTEgKSAqIChsZW5ndGgobGV2ZWxzKGluZGVfdGVzdCR5KSkgLTEgKQpwcmludCgiQ3JpdGljYWwgVmFsdWUgb2YgRGVmYXVsdCBhbmQgWSBUYWJsZToiKQpxY2hpc3EoMC45NSwgZGYpCnByaW50KCJDcmFtZXIuViBvZiBEZWZhdWx0IGFuZCBZOiIpCmRlZmF1bHRfdiA8LSBjcmFtZXIudihkZWZhdWx0X3kpCmRlZmF1bHRfdgpgYGAKCnAtdmFsdWUgaXMgbGFyZ2VyIHRoYW4gMC4wNSwgY2hpLXNxdWFyZWQgdmFsdWUgaXMgc21hbGxlciB0aGFuIGNyaXRpY2FsIHZhbHVlLCBzbyBmYWlsIHRvIHJlamVjdCB0aGUgbnVsbCBoeXBvdGhlc2lzLiBXZSBjYW5ub3QgY29uY2x1ZGUgdGhhdCAiZGVmYXVsdCIgYW5kICJ5IiBpcyBhc3NvY2lhdGVkLgoKCl9fbWFyaXRhbF9fCgoKYGBge3J9CmxpYnJhcnkocXVlc3Rpb25yKQpwcmludCgiQ29udGluZ2VuY3kgVGFibGUgb2YgTWFyaXRhbCBhbmQgWToiKQptYXJpdGFsX3kgPC0gdGFibGUoaW5kZV90ZXN0JG1hcml0YWwsIGluZGVfdGVzdCR5KQptYXJpdGFsX3kgCgpwcmludCgiQ2hpLVNxdWFyZWQgVGVzdCBvZiBNYXJpdGFsIGFuZCBZIFRhYmxlOiIpCmNoaXNxLnRlc3QobWFyaXRhbF95KSAKZGYgPC0gKGxlbmd0aChsZXZlbHMoaW5kZV90ZXN0JG1hcml0YWwpKSAtMSApICogKGxlbmd0aChsZXZlbHMoaW5kZV90ZXN0JHkpKSAtMSApCnByaW50KCJDcml0aWNhbCBWYWx1ZSBvZiBNYXJpdGFsIGFuZCBZIFRhYmxlOiIpCnFjaGlzcSgwLjk1LCBkZikKcHJpbnQoIkNyYW1lci5WIG9mIE1hcml0YWwgYW5kIFk6IikKbWFyaXRhbF92IDwtIGNyYW1lci52KG1hcml0YWxfeSkKbWFyaXRhbF92CmBgYAoKTm8gYXNzb2NpYXRpb24gYmV0d2VlbiAibWFyaXRhbCIgYW5kICJ5Ii4KCl9fam9iIGFuZCBkZWZhdWx0X18KCmBgYHtyfQpsaWJyYXJ5KHF1ZXN0aW9ucikKcHJpbnQoIkNvbnRpbmdlbmN5IFRhYmxlIG9mIERlZmF1bHQgYW5kIEpvYjoiKQpkZWZhdWx0X2pvYl9uZXcgPC0gdGFibGUoaW5kZV90ZXN0JGRlZmF1bHQsIGluZGVfdGVzdCRuZXdfam9icykKZGVmYXVsdF9qb2JfbmV3IAoKcHJpbnQoIkNoaS1TcXVhcmVkIFRlc3Qgb2YgQ29udGFjdCBhbmQgSm9iIFRhYmxlOiIpCmNoaXNxLnRlc3QoZGVmYXVsdF9qb2JfbmV3KSAKZGYgPC0gKGxlbmd0aChsZXZlbHMoaW5kZV90ZXN0JGRlZmF1bHQpKSAtMSApICogKGxlbmd0aChsZXZlbHMoaW5kZV90ZXN0JG5ld19qb2JzKSkgLTEgKQpwcmludCgiQ3JpdGljYWwgVmFsdWUgb2YgRGVmYXVsdCBhbmQgSm9iIFRhYmxlOiIpCnFjaGlzcSgwLjk1LCBkZikKcHJpbnQoIkNyYW1lci5WIG9mIERlZmF1bHQgYW5kIEpvYjoiKQpkZWZhdWx0X2pvYl9uZXdfdiA8LSBjcmFtZXIudihkZWZhdWx0X2pvYl9uZXcpCmRlZmF1bHRfam9iX25ld192CmBgYAoKTm8gYXNzb2NpYXRpb24gYmV0d2VlbiBqb2IgYW5kIGRlZmF1bHQuCgoKX19WaXN1YWxpemUgdGhlIHJlc3VsdHNfXwoKYGBge3J9CnZhciA8LSBjKCJqb2IiLCAicG91dGNvbWUiLCAibW9udGgiLCAicHJldmlvdXNfYm9vbCIsICJlZHVjYXRpb24iLCAiaG91c2luZyIsICJsb2FuIiwKICAgICAgICAgImNvbnRhY3QiLCAibWFyaXRhbCIsICJkZWZhdWx0IikKdiA8LSBjKGpvYl92LCBwb3V0Y29tZV92LCBtb250aF92LCBwcmV2aW91c19ib29sX3YsIGVkdWNhdGlvbl92LCBob3VzaW5nX3YsIGxvYW5fdiwgY29udGFjdF92LCBtYXJpdGFsX3YsCiAgICAgICBkZWZhdWx0X3YpCgpjaGlfdGVzdCA8LSBkYXRhLmZyYW1lKHZhciwgdikKbmFtZXMoY2hpX3Rlc3QpIDwtIGMoIlZhcmlhYmxlcyIsICJDcmFtZXIuViIpCmNoaV90ZXN0CmBgYAoKYGBge3J9CmxpYnJhcnkoZ2dwbG90MikKZ2dwbG90KGRhdGE9Y2hpX3Rlc3QsIGFlcyh4PXJlb3JkZXIoVmFyaWFibGVzLCAtQ3JhbWVyLlYpLCB5PUNyYW1lci5WLCBmaWxsPVZhcmlhYmxlcykpICsKICBnZW9tX2JhcihzdGF0PSJpZGVudGl0eSIpKyAKICB0aGVtZV9jbGFzc2ljKCkgKyB5bGFiKCJDcmFtZXIncyBWIikgKyB4bGFiKCJDYXRlZ29yaWNhbCBWYXJpYWJsZXMiKSArCiAgZ2d0aXRsZSgiQ3JhbWVyJ3MgViBvZiBDYXRlZ29yaWNhbCBWYXJpYWJsZXMiKSArCiAgdGhlbWUocGxvdC50aXRsZSA9IGVsZW1lbnRfdGV4dChoanVzdCA9IDAuNSwgc2l6ZT0xNCwgZmFjZT0iYm9sZCIpLCAKICAgICAgICBheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChhbmdsZSA9IDQ1LCBoanVzdCA9IDEsIGZhY2U9ImJvbGQiLCBzaXplPTE0KSkKICAKCmBgYAoKCiMjIyBMb2dpc3RpYyBSZWdyZXNzaW9uIAoKTG9naXN0aWMgUmVncmVzc2lvbiBpcyBmb3IgYSBjb250aW5vdXMgdmFyaWJsZSBhbmQgYSBjYXRlZ29yaWNhbCB2YXJpYWJsZS4gVmFyaWFibGVzIGluY2x1ZGU6IGR1cmF0aW9uLCBhZ2UsIGJhbGFuY2UsIGNhbXBhaWduLCBwZGF5cywgZGF5CgpCZXR3ZWVuIHByZWRpY3RvciB2YXJpYWJsZXM6IGFnZSBhbmQgaG91c2luZwoKMS4gQnVpbGQgYSBsb2dpc3RpYyByZWdyZXNzaW9uIG1vZGVsIFxuCjIuIENoZWNrIHRoZSBjb3JyZWxhdGlvbiBlc3RpbWF0ZSBcbgozLiBDb21wYXJlIHRoZSByZXN1bHQgYW5kIHNlbGVjdCB0aGUgZmVhdHVyZXMgdG9nZXRoZXIgd2l0aCB0aGUgdmlzdWFsaXphdGlvbiBzdW1tYXJ5CgpfX2R1cmF0aW9uX18KCmBgYHtyfQpscl9kdXJhdGlvbiA8LSBnbG0oeSB+IGR1cmF0aW9uLCBkYXRhID0gaW5kZV90ZXN0LCBmYW1pbHkgPSAiYmlub21pYWwiKQpzdW1tYXJ5KGxyX2R1cmF0aW9uKSRjb2VmZmljaWVudApgYGAKCgpfX2FnZV9fCgpgYGB7cn0KbHJfYWdlIDwtIGdsbSh5IH4gZHVyYXRpb24gKyBhZ2UsIGRhdGEgPSBpbmRlX3Rlc3QsIGZhbWlseSA9ICJiaW5vbWlhbCIpCnN1bW1hcnkobHJfYWdlKSRjb2VmZmljaWVudApgYGAKCmBgYHtyfQpsciA8LSBnbG0oeSB+IGR1cmF0aW9uICsgYWdlICsgYmFsYW5jZSArIGNhbXBhaWduICsgcGRheXMgKyBkYXksIGRhdGEgPSBpbmRlX3Rlc3QsIGZhbWlseSA9ICJiaW5vbWlhbCIpCnN1bW1hcnkobHIpCmBgYAoKYGBge3J9CmxpYnJhcnkoY2FyZXQpCmltcCA8LSBhcy5kYXRhLmZyYW1lKHZhckltcChscikpCmltcCA8LSBkYXRhLmZyYW1lKG92ZXJhbGwgPSBpbXAkT3ZlcmFsbCwKICAgICAgICAgICBuYW1lcyAgID0gcm93bmFtZXMoaW1wKSkKbHJfdGVzdCA8LSBpbXBbb3JkZXIoaW1wJG92ZXJhbGwsZGVjcmVhc2luZyA9IFQpLF0KbmFtZXMobHJfdGVzdCkgPC0gYygiSW1wb3J0YW5jZSIsICJWYXJpYWJsZXMiKQpscl90ZXN0CmBgYAoKCl9fT25lLVdheSBBTk9WQSBUZXN0IGZvciBBZ2UgYW5kIFlfXwoKImFnZSIgaXMgbm9ybWFsIGRpc3RyaWJ1dGlvbiwgc28gaXQgY2FuIGFwcGx5IGFub3ZhIHRlc3QuCgpgYGB7cn0Kb25ld2F5LnRlc3QoYWdlfiB5LCBiYW5rLCB2YXIuZXF1YWw9VCkKYGBgCgpDcml0aWNhbCB2YWx1ZQoKYGBge3J9CnFmKDAuOTUsIDEsIDgyMjMyKQpgYGAKCkYgdmFsdWUgPj0gQ3JpdGljYWwgVmFsdWUsIGFuZCBwLXZhbHVlIDwgMC4wNSwgc28gd2UgcmVqZWN0IHRoZSBudWxsIGh5cG90aGVzaXMuIFRoZXJlIGlzIGFzc29jaWF0aW9uIGJldHdlZW4gImFnZSIgYW5kICJ5Ii4KCgpfX1Zpc3VhbGl6ZSB0aGUgdGVzdCByZXN1bHRzX18KCmBgYHtyfQpsaWJyYXJ5KGdncGxvdDIpCmdncGxvdChkYXRhPWxyX3Rlc3QsIGFlcyh4PXJlb3JkZXIoVmFyaWFibGVzLCAtSW1wb3J0YW5jZSksIHk9SW1wb3J0YW5jZSwgZmlsbD1WYXJpYWJsZXMpKSArCiAgZ2VvbV9iYXIoc3RhdD0iaWRlbnRpdHkiKSsKICB0aGVtZV9jbGFzc2ljKCkgKyB5bGFiKCJJbXBvcnRhbmNlIikgKyB4bGFiKCJDb250aW51b3VzIFZhcmlhYmxlcyIpICsKICBnZ3RpdGxlKCJJbXBvcnRhbmNlIG9mIENvbnRpbm91cyBWYXJpYWJsZXMiKSArCiAgdGhlbWUocGxvdC50aXRsZSA9IGVsZW1lbnRfdGV4dChoanVzdCA9IDAuNSwgc2l6ZT0xNCwgZmFjZT0iYm9sZCIpLCBheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dCgKICAgIGZhY2U9ImJvbGQiLCBhbmdsZSA9IDQ1LCBoanVzdCA9IDEsIHNpemU9MTQpKQoKYGBgCgpfX0ZlYXR1cmUgU2VsZWN0aW9uX18KCmZlYXR1cmVfZ3JvdXAxOiBtb250aCwgcG91dGNvbWUsIGNvbnRhY3QsIGR1cmF0aW9uCmZlYXR1cmVfZ3JvdXAyOiBtb250aCwgcG91dGNvbWUsIGNvbnRhY3QsIGR1cmF0aW9uLCBqb2IsIGhvdXNpbmcsIHByZXZpb3VzX2Jvb2wKCgojQ2xhc3NpZmljYXRpb24KCmBgYHtyfQpiYW5rX2Jhc2UgPC0gYmFuawpgYGAKClRoZSBvcmRlciBvZiBsZXZlbCBpcyBieSBhbHBoYWJldC4gIk5vIiBpcyB0aGUgcG9zaXRpdmUgY2xhc3MgYnkgZGVmYXVsdC4gQ2hhbmdlIHRoZSBvcmRlciBvZiB0aGUgbGV2ZWxzLCBzbyAieWVzIiB3aWxsIGJlIHRoZSBwb3NpdGl2ZSBjbGFzcyBpbiB0aGUgbW9kZWwuCgpgYGB7cn0KYmFua19iYXNlJHkgPSBmYWN0b3IoYmFua19iYXNlJHksbGV2ZWxzKGJhbmtfYmFzZSR5KVtjKDIsMSldKQpgYGAKCmBgYHtyfQpsZXZlbHMoYmFua19iYXNlJHkpCmBgYAoKTm93IHRoZSAieWVzIiB3aWxsIGJlIHRoZSBwb3NpdGl2ZSBjbGFzcyBpbiB0aGUgbW9kZWxzCgojIyNCYXNlbGluZSBNb2RlbHMKClR3byBiYXNlbGluZSBjbGFzc2lmaWN0aW9uIG1vZGVscyBhcmUgY3JlYXRlZCB1c2luZyBkZWNpc2lvbiB0cmVlIGFuZCByYW5kb20gZm9yZXN0IHdpdGggYWxsIHRoZSAxNiB2YXJpYWJsZXMuIAoKYGBge3J9CiMjIDc1JSBvZiB0aGUgc2FtcGxlIHNpemUKc2l6ZV9iYXNlIDwtIGZsb29yKDAuNzUgKiBucm93KGJhbmtfYmFzZSkpCgojIyBzZXQgdGhlIHNlZWQgdG8gbWFrZSB5b3VyIHBhcnRpdGlvbiByZXByb2R1Y2libGUKc2V0LnNlZWQoMTIzKQojIHNhbXBsZShzZXFfbGVuKG5yb3coYmFuaykpLCBzaXplID0gc2l6ZTEpIC0+IHRoZSBpbmRleCBvZiB0cmFpbmluZyBzZXQKdHJhaW4gPC0gYmFua19iYXNlW3NhbXBsZShzZXFfbGVuKG5yb3coYmFua19iYXNlKSksIHNpemUgPSBzaXplX2Jhc2UpLCBdCnRlc3QgPC0gYmFua19iYXNlWy1zYW1wbGUoc2VxX2xlbihucm93KGJhbmtfYmFzZSkpLCBzaXplID0gc2l6ZV9iYXNlKSwgXQpgYGAKCl9fQ3JlYXRlIGEgZGVjaXNpb24gdHJlZV9fCgpgYGB7cn0KbGlicmFyeShycGFydCkKYmFzZV9kdCA8LSBycGFydCh5IH4gLiwgdHJhaW4pCmBgYAoKYGBge3J9CnByaW50Y3AoYmFzZV9kdCkKYGBgCgpgYGB7cn0KcGxvdGNwKGJhc2VfZHQpCmBgYAoKYGBge3J9CiNvcHRpbWFsIGNwIGZvciBiYXNlbGluZSB0cmVlCm9wdF9pbmRleCA8LSB3aGljaC5taW4oYmFzZV9kdCRjcHRhYmxlWywgInhlcnJvciJdKQpjcF9vcHQgPC0gYmFzZV9kdCRjcHRhYmxlW29wdF9pbmRleCwgIkNQIl0KY3Bfb3B0CmBgYAoKCgpgYGB7cn0KbGlicmFyeShycGFydC5wbG90KQpycGFydC5wbG90KGJhc2VfZHQpCmBgYAoKTWFrZSBwcmVkaWN0aW9uIG9uIHRoZSB0ZXN0IHNldAoKYGBge3J9CnRlc3QkQmFzZURUX1ByZWRpY3QgPC0gcHJlZGljdChiYXNlX2R0LCB0ZXN0WywgMToxNl0sIHR5cGUgPSAiY2xhc3MiKQpgYGAKCkNvbXBhcmUgdGhlIHRydWUgeSBhbmQgdGhlIHByZWRpY3RlZCB5CgpgYGB7cn0KaGVhZCh0ZXN0WywgMTc6MThdKQpgYGAKCgojIyMgRXhwZXhyaW1lbnRhbCBNb2RlbHMKCkJ1aWxkIG1vZGVscyB3aXRoIHRoZSBmZWF0dXJlcyBzZWxlY3RlZC4gRmVhdHVyZSBncm91cCAxOiBtb250aCArIHBvdXRjb21lICsgY29udGFjdCArIGR1cmF0aW9uOyBGZWF0dXJlIGdyb3VwIDI6IG1vbnRoICsgcG91dGNvbWUgKyBjb250YWN0ICsgZHVyYXRpb24gKyBqb2IgKyBob3VzaW5nICsgcHJldmlvdXNfYm9vbAoKTWFrZSBhIGNvcHkgb2YgYmFuay4gQ3JlYXRlIGEgbmV3IGNvbHVtbiAicHJldmlvdXNfYm9vbCIgIzE4CgpgYGB7cn0KYmFua19leHAgPC0gYmFua19iYXNlCiNyZXBsYWNlIHRoZSBvcmlnbmFsIHByZXZpb3VzIChudW1lcmljKSB3aXRoIGJvb2xlYW4KYmFua19leHAkcHJldmlvdXMgPC0gYXMuZmFjdG9yKGJhbmtfZXhwJHByZXZpb3VzPjApCmBgYAoKYGBge3J9CmhlYWQoYmFua19leHApCmBgYAoKCkZvciB0aGUgbW9kZWxzIHVzZSB0aGUgZmVhdHVyZSBncm91cCAxLCB0aGUgdHJhaW5pbmcgc2V0IGFuZCB0ZXN0IHNldCBhcmUgdGhlIHNhbWUgYXMgdGhlIGJhc2UgbGluZSBtb2RlbC4gRm9yIHRoZSBtb2RlbHMgdXNlIHRoZSBmZWF0dXJlIGdyb3VwIDIsIHVzZSB0aGUgdHJhaW5pbmcgYW5kIHRlc3Qgc2V0IGJlbG93OgoKYGBge3J9CiMjIDc1JSBvZiB0aGUgc2FtcGxlIHNpemUKc2l6ZV9leHAgPC0gZmxvb3IoMC43NSAqIG5yb3coYmFua19leHApKQoKIyMgc2V0IHRoZSBzZWVkIHRvIG1ha2UgeW91ciBwYXJ0aXRpb24gcmVwcm9kdWNpYmxlCnNldC5zZWVkKDEyMykKIyBzYW1wbGUoc2VxX2xlbihucm93KGJhbmspKSwgc2l6ZSA9IHNpemUxKSAtPiB0aGUgaW5kZXggb2YgdHJhaW5pbmcgc2V0CnRyYWluX2V4cCA8LSBiYW5rX2V4cFtzYW1wbGUoc2VxX2xlbihucm93KGJhbmtfZXhwKSksIHNpemUgPSBzaXplX2V4cCksIF0KdGVzdF9leHAgPC0gYmFua19leHBbLXNhbXBsZShzZXFfbGVuKG5yb3coYmFua19leHApKSwgc2l6ZSA9IHNpemVfZXhwKSwgXQpgYGAKCgpUaGUgdHJhaW5pbmcgc2V0IGFuZCB0ZXN0IHNldCBhcmUgdGhlIHNhbWUgYXMgaW4gdGhlIGJhc2VsaW5lIG1vZGVscyBiZWNhdXNlIHRoZSBzZWVkIGlzIHRoZSBzYW1lLgoKX19EZWNpc2lvbiBUcmVlX18KCmNwICB5ZGVmYXVsdCA9MC4wMQoKYGBge3J9CmxpYnJhcnkocnBhcnQpCiMgZGVjaXNpb24gdHJlZSB3aXRoIGZlYXR1cmUgZ3JvdXAgMQp0cmVlMSA8LSBycGFydCh5IH4gbW9udGggKyBwb3V0Y29tZSArIGNvbnRhY3QgKyBkdXJhdGlvbiwgZGF0YSA9IHRyYWluKQoKIyBkZWNpc2lvbiB0cmVlIHdpdGggZmVhdHVyZSBncm91cCAyLCB0aGUgInByZXZpb3VzIiBpcyBib29sLCBidXQgd2l0aCB0aGUgc2FtZSBuYW1lCnRyZWUyIDwtIHJwYXJ0KHkgfiBtb250aCArIHBvdXRjb21lICsgY29udGFjdCArIGR1cmF0aW9uICsgam9iICsKICAgICAgICAgICAgICAgICAgaG91c2luZyArIHByZXZpb3VzLCBkYXRhID0gdHJhaW5fZXhwKQpgYGAKCgpnZXQgdGhlIG9wdGltYWwgY3AgYmFzZWQgb24gY3Jvc3MtdmFsaWRhdGlvbiBlcnJvcgoKYGBge3J9CnByaW50Y3AodHJlZTEpCmBgYAoKYGBge3J9CnBsb3RjcCh0cmVlMSkKYGBgCgoKYGBge3J9CnByaW50Y3AodHJlZTIpCmBgYAp0cmVlMiBzZWVtcyB0byBiZSB0aGUgc2FtZSBhcyBiYXNlbGluZSB0cmVlCgpgYGB7cn0KcGxvdGNwKHRyZWUyKQpgYGAKCgpgYGB7cn0KI29wdGltYWwgY3AgZm9yIHRyZWUxCm9wdF9pbmRleDEgPC0gd2hpY2gubWluKHRyZWUxJGNwdGFibGVbLCAieGVycm9yIl0pCmNwX29wdDEgPC0gdHJlZTEkY3B0YWJsZVtvcHRfaW5kZXgxLCAiQ1AiXQoKI29wdGltYWwgY3AgZm9yIHRyZWUyCm9wdF9pbmRleDIgPC0gd2hpY2gubWluKHRyZWUyJGNwdGFibGVbLCAieGVycm9yIl0pCmNwX29wdDIgPC0gdHJlZTIkY3B0YWJsZVtvcHRfaW5kZXgyLCAiQ1AiXQoKY3Bfb3B0MQpjcF9vcHQyCgpgYGAKCgpgYGB7cn0KbGlicmFyeShycGFydC5wbG90KQpycGFydC5wbG90KHRyZWUxKQpycGFydC5wbG90KHRyZWUyKQpgYGAKCk5vdCBhbGwgdGhlIGlucHV0IHZhcmlhYmxlcyBhcmUgdXNlZC4KCk1ha2UgcHJlZGljdGlvbiBvbiB0aGUgdGVzdCBzZXQKCmBgYHtyfQp0ZXN0JFRyZWUxX1ByZWRpY3QgPC0gcHJlZGljdCh0cmVlMSwgdGVzdFssIDE6MTZdLCB0eXBlID0gImNsYXNzIikKdGVzdF9leHAkVHJlZTJfUHJlZGljdCA8LSBwcmVkaWN0KHRyZWUyLCB0ZXN0X2V4cFssIDE6MTZdLCB0eXBlID0gImNsYXNzIikKCmBgYAoKQ29tcGFyZSB0aGUgdHJ1ZSB5IGFuZCB0aGUgcHJlZGljdGVkIHkKCmBgYHtyfQpoZWFkKHRlc3RbLCBjKDE3LCAxOSldKQpoZWFkKHRlc3RfZXhwWywgYygxNywgMTgpXSkKYGBgCgpfX1JhbmRvbSBGb3Jlc3RfXwoKYGBge3J9CmxpYnJhcnkocmFuZG9tRm9yZXN0KQojIHJhbmRvbSBmb3Jlc3Qgd2l0aCBmZWF0dXJlIGdyb3VwIDEKZm9yZXN0MTwtIHJhbmRvbUZvcmVzdCh5IH4gbW9udGggKyBwb3V0Y29tZSArIGNvbnRhY3QgKyBkdXJhdGlvbiwgZGF0YSA9IHRyYWluKQpgYGAKCgpgYGB7cn0KbGlicmFyeShyYW5kb21Gb3Jlc3QpCiMgcmFuZG9tIGZvcmVzdCB3aXRoIGZlYXR1cmUgZ3JvdXAgMgpmb3Jlc3QyPC0gcmFuZG9tRm9yZXN0KHkgfiBtb250aCArIHBvdXRjb21lICsgY29udGFjdCArIGR1cmF0aW9uICsgam9iICsKICAgICAgICAgICAgICAgICAgaG91c2luZyArIHByZXZpb3VzLCBkYXRhID0gdHJhaW5fZXhwKQpgYGAKCnJ1bnMgbG9uZyEKCk1ha2UgcHJlZGljdGlvbiBvbiB0ZXN0IHNldAoKCmBgYHtyfQpsaWJyYXJ5KHJhbmRvbUZvcmVzdCkKdGVzdCRGb3Jlc3QxX1ByZWRpY3QgPC0gcHJlZGljdChmb3Jlc3QxLCB0ZXN0WywgMToxNl0pCnRlc3RfZXhwJEZvcmVzdDJfUHJlZGljdCA8LSBwcmVkaWN0KGZvcmVzdDIsIHRlc3RfZXhwWywgMToxNl0pCmBgYAoKCiMjIyBFdmFsdWF0ZSB0aGUgcGVyZm9ybWFuY2Ugb2YgdGhlIG1vZGVscwogCl9fYmFzZWxpbmUgZGVjaXNpb24gdHJlZV9fIAogCmBgYHtyfQpsaWJyYXJ5KGNhcmV0KQpjbV9iYXNlZHQgPC0gY29uZnVzaW9uTWF0cml4KHRlc3QkQmFzZURUX1ByZWRpY3QsIHRlc3QkeSkKY21fYmFzZWR0CmBgYAoKCl9fZXhwZXJpbWVudGFsIGRlY2lzaW9uIHRyZWUgd2l0aCBmZWF0dXJlIGdyb3VwIDFfXwoKYGBge3J9CmxpYnJhcnkoY2FyZXQpCmNtX3RyZWUxPC1jb25mdXNpb25NYXRyaXgodGVzdCRUcmVlMV9QcmVkaWN0LCB0ZXN0JHkpCmNtX3RyZWUxCmBgYAoKX19leHBlcmltZW50YWwgZGVjaXNpb24gdHJlZSB3aXRoIGZlYXR1cmUgZ3JvdXAgMl9fCgpgYGB7cn0KbGlicmFyeShjYXJldCkKY21fdHJlZTI8LWNvbmZ1c2lvbk1hdHJpeCh0ZXN0X2V4cCRUcmVlMl9QcmVkaWN0LCB0ZXN0X2V4cCR5KQpjbV90cmVlMgpgYGAKCl9fZXhwZXJpbWVudGFsIHJhbmRvbSBmb3Jlc3Qgd2l0aCBmZWF0dXJlIGdyb3VwIDFfXwoKYGBge3J9CmxpYnJhcnkoY2FyZXQpCmNtX2ZvcmVzdDE8LWNvbmZ1c2lvbk1hdHJpeCh0ZXN0JEZvcmVzdDFfUHJlZGljdCwgdGVzdCR5KQpjbV9mb3Jlc3QxCmBgYAoKX19leHBlcmltZW50YWwgcmFuZG9tIGZvcmVzdCB3aXRoIGZlYXR1cmUgZ3JvdXAgMl9fCgpgYGB7cn0KbGlicmFyeShjYXJldCkKY21fZm9yZXN0MjwtY29uZnVzaW9uTWF0cml4KHRlc3RfZXhwJEZvcmVzdDJfUHJlZGljdCwgdGVzdF9leHAkeSkKY21fZm9yZXN0MgpgYGAKCgpgYGB7cn0KZm9yZXN0MSRpbXBvcnRhbmNlCmBgYAoKYGBge3J9CmZvcmVzdDIkaW1wb3J0YW5jZQpgYGAKCgpfX1JPQyBDdXJ2ZV9fCgoKYGBge3J9CmxpYnJhcnkoJ3BST0MnKQpwbG90KHJvYyh0ZXN0JHksIHByZWRpY3QoYmFzZV9kdCwgdGVzdFssMToxNl0sIHR5cGUgPSAicHJvYiIpWywyXSksIGNvbD0iZGFya2dvbGRlbnJvZDEgIiwgbGVnYWN5LmF4ZXM9VCwgeGxhYj0iRmFsc2UgUG9zaXRpdmUgUmF0ZSAoMS1TcGVjaWZpY2l0eSkiLCB5bGFiPSJUcnVlIFBvc2l0aXZlIFJhdGUgKFNlbnNpdGl2aXR5KSIsIG1haW49IkNvbXBhcmUgTW9kZWxzIikKcGxvdChyb2ModGVzdCR5LCBwcmVkaWN0KHRyZWUxLCB0ZXN0WywxOjE2XSwgdHlwZSA9ICJwcm9iIilbLDJdKSwgYWRkPVQsIGNvbD0iYXF1YW1hcmluZSIpCnBsb3Qocm9jKHRlc3RfZXhwJHksIHByZWRpY3QodHJlZTIsIHRlc3RfZXhwWywxOjE2XSwgdHlwZSA9ICJwcm9iIilbLDJdKSwgYWRkPVQsIGNvbD0iY29yYWwiKQpwbG90KHJvYyh0ZXN0JHksIHByZWRpY3QoZm9yZXN0MSwgdGVzdFssMToxNl0sIHR5cGUgPSAicHJvYiIpWywyXSksIGFkZD1ULCBjb2w9ImNoYXJ0cmV1c2UxIikKcGxvdChyb2ModGVzdF9leHAkeSwgcHJlZGljdChmb3Jlc3QyLCB0ZXN0X2V4cFssMToxNl0sIHR5cGUgPSAicHJvYiIpWywyXSksIGFkZD1ULCBjb2w9InB1cnBsZSIpCmxlZ2VuZCgiYm90dG9tcmlnaHQiLCBsZWdlbmQ9YygiQmFzZWxpbmUgVHJlZSIsICJUcmVlMSIsICJUcmVlMiIsICJGb3Jlc3QxIiwgIkZvcmVzdDIiKSxjb2w9YygiZGFya2dvbGRlbnJvZDEiLCAiYXF1YW1hcmluZSIsICJjb3JhbCIsICJjaGFydHJldXNlMSIsICJwdXJwbGUiKSwgbHdkPTIpCgpgYGAKClJhbmRvbSBGb3Jlc3Qgd29ya3MgYmV0dGVyIHRoYW4gRGVjaXNpb24gVHJlZS4gRmVhdHVyZSBncm91cCAyIGlzIGJldHRlciB0aGFuIGZlYXR1cmUgZ3JvdXAgMS4gRGVjaXNpb24gdHJlZSB3aXRoIGZlYXR1cmUgZ3JvdXAgMiBpcyB0aGUgc2FtZSBhcyBiYXNlbGluZSBkZWNpc2lvbiB0cmVlLiBUaGUgdHdvIHRyZWUgaXMgZXhhY3RseSB0aGUgc2FtZS4gRGVjaXNpb24gVHJlZSB3aXRoIGZlYXR1cmUgZ3JvdXAgMSBpcyBzbGlnaHRseSB3b3JzZSB0aGFuIGJhc2VsaW5lLgoKCmBgYHtyfQpjbSA8LSBjKGNtX2Jhc2VkdCRieUNsYXNzWzFdLCBjbV90cmVlMSRieUNsYXNzWzFdLCBjbV90cmVlMiRieUNsYXNzWzFdLCBjbV9mb3Jlc3QxJGJ5Q2xhc3NbMV0sCiAgICAgICAgY21fZm9yZXN0MiRieUNsYXNzWzFdKQpjbV9kZiA8LSBkYXRhLmZyYW1lKGNtKQpjbV9kZiRNb2RlbCA8LSBjKCJCYXNlbGluZSBUcmVlIiwgIlRyZWUxIiwgIlRyZWUyIiwgIkZvcmVzdDEiLCAiRm9yZXN0MiIpCmNvbG5hbWVzKGNtX2RmKTwtIGMoIlNlbnNpdGl2aXR5IiwgIk1vZGVsIikKY21fZGYgPC0gY21fZGZbLCBjKDIsMSldCmNtX2RmCmBgYAoKYGBge3J9CmxpYnJhcnkoZ2dwbG90MikKZ2dwbG90KGRhdGE9Y21fZGYsIGFlcyh4PXJlb3JkZXIoTW9kZWwsIC1TZW5zaXRpdml0eSksIHk9U2Vuc2l0aXZpdHksIGZpbGw9TW9kZWwpKSArCiAgZ2VvbV9iYXIoc3RhdD0iaWRlbnRpdHkiKSsgCiAgdGhlbWVfY2xhc3NpYygpICsgeWxhYigiU2Vuc2l0aXZpdHkiKSArIHhsYWIoIk1vZGVsIikgKwogIGdndGl0bGUoIkNvbXBhcmUgTW9kZWwgU2Vuc2l0aXZpdHkiKSArCiAgdGhlbWUocGxvdC50aXRsZSA9IGVsZW1lbnRfdGV4dChoanVzdCA9IDAuNSwgc2l6ZT0xNCwgZmFjZT0iYm9sZCIpLCAKICAgICAgICBheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChhbmdsZSA9IDQ1LCBoanVzdCA9IDEsIGZhY2U9ImJvbGQiLCBzaXplPTE0KSkKICAKCmBgYAoKCgotLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0KCl9fQURELU9OOiBDcmVhdGUgYSBiYXNlbGluZSByYW5kb20gZm9yZXN0X18KClRoaXMgd2lsbCBub3QgZ28gdG8gcmVwb3J0CgpkZWZhdWx0IG50cmVlPTUwMAoKYGBge3J9CmxpYnJhcnkocmFuZG9tRm9yZXN0KQpiYXNlX3JmMTwtIHJhbmRvbUZvcmVzdCh5IH4gLiwgZGF0YSA9IHRyYWluKQpgYGAKCgoKCk1ha2UgcHJlZGljdGlvbiBvbiB0aGUgdGVzdCBzZXQKCmBgYHtyfQp0ZXN0JEJhc2VSRjFfUHJlZGljdCA8LSBwcmVkaWN0KGJhc2VfcmYxLCB0ZXN0WywgMToxNl0sIHR5cGUgPSAiY2xhc3MiKQpgYGAKCkNvbXBhcmUgdGhlIHRydWUgeSBhbmQgdGhlIHByZWRpY3RlZCB5CgpgYGB7cn0KaGVhZCh0ZXN0WywgYygxNywxOSldKQpgYGAKCm50cmVlID0gNTAwCgpgYGB7cn0KbGlicmFyeShjYXJldCkKY21fYmFzZXJmIDwtIGNvbmZ1c2lvbk1hdHJpeCh0ZXN0JEJhc2VSRjFfUHJlZGljdCwgdGVzdCR5KQpjbV9iYXNlcmYKYGBgCgoK